2016-12-09 3 views
0

Следующий код не будет компилироваться, потому что, по-видимому, так работают защищенные члены. (Есть вопросы, ответы на которые почему-то на SO, например, here).Что может пойти не так, если я использую бросок для доступа к защищенному члену?

class Parent { 
protected: 
    int value; 
}; 

class Child : public Parent { 
public: 
    void set_value_of(Parent* x, int value) { 
     x->value = value; 
    } 
}; 

int main() { 
} 

Например, GCC сообщает

main.cpp: In member function 'void Child::set_value_of(Parent*, int)': 
main.cpp:11:16: error: 'int Parent::value' is protected within this context 
      x->value = value; 
       ^~~~~ 
main.cpp:5:13: note: declared protected here 
     int value; 
      ^~~~~ 

Что может пойти не так, если вы накладываете x к Child? Это случай никогда не делайте этого или вы можете, если знаете, что делаете?

void set_value_of(Parent* x, int value) { 
     static_cast<Child*>(x)->value = value; 
    } 


Редактировать К сожалению, все предложения для получения вокруг этого злоупотребления до сих пор не помогает мне в решении моей проблемы проектирования, поэтому я стараюсь быть менее общими и показать мою actualy реального мира дело.

Основная цель состоит в том, чтобы не полагаться на friend декларации, как Node класса не должен быть изменен (то есть. Не друг декларация не должна должна быть добавлена), для реализации другого подкласса как Group.

class Node { 
protected: 
    Node* _parent; 
}; 

class Group : public Node { 
protected: 
    std::vector<Node*> _nodes; 
public: 
    void insert(Node* node) { 
    if (node->_parent) { 
     throw std::runtime_error("node already has a parent"); 
    } 
    _nodes.push_back(node); 
    node->_parent = this; 
    } 
}; 

Это может быть решена путем добавления friend class Group в Node классе, но я искал способ вокруг него.

+0

Что вы пытаетесь сделать? Почему у одного класса есть метод, который работает в другом классе? –

+0

Я связал тот самый вопрос, это должно быть дубликат .. Я понял, что это не тот же вопрос, duh –

+0

То, что вы делаете, действительно, если вы делаете 'dynamic_cast' вместо этого, и сравниваете полученный указатель с' nullptr' первый. 'dynamic_cast' гарантирует, что он вернет только действительный указатель, если литой объект действительно является указателем этого типа. – Xirema

ответ

1

Обратите внимание, что, принимая во внимание ваш фактический код, я хотел бы предложить рефакторинга, если Group не предназначен, чтобы быть особый тип Node. См. Мою заметку внизу этого ответа.


Если вы можете изменить Parent, один из способов сделать это было бы, чтобы обеспечить функцию protected члена в качестве помощника; учитывая, что set_value_of() принимает экземпляр и значение, он должен, вероятно, быть static.

class Parent { 
protected: 
    int value; 

    // Like this. 
    static void set_value_of(Parent* x, int value) { x->value = value; } 
}; 

class Child : public Parent { 
public: 
    // Provide set_value_of() to the world. 
    using Parent::set_value_of; 

    // Alternatively... 
    //static void set_value_of(Parent* x, int value) { 
    // Parent::set_value_of(x, value); 
    //} 


    // For example usage. 
    int get_value() const { return value; } 
}; 

int main() { 
    Child c1, c2; 

    c1.set_value_of(&c2, 33); 
    c2.set_value_of(&c1, 42); 
    Child::set_value_of(&c2, 3); 

    std::cout << c1.get_value() << ", " << c2.get_value() << '\n'; 
} 

При этом выход будет 42, 3, как и ожидалось.


Учитывая ваш фактический код, это все еще можно реализовать. Проблема заключается в том, что Node не обеспечивает способ воздействия внешних воздействий на безопасное его изменение, что, в свою очередь, означает, что он не обеспечивает способ для Group для безопасного его изменения. (В то время как Group действительно может попытаться отличить Node* до Group*, это может сильно ошибиться, и это, вероятно, смущает тех, кто выполняет техническое обслуживание в будущем.) Поэтому, если Node предназначен для базового класса и оставить фактически управление различными узлов к подклассам, он должен предоставить им возможность сказать, что делать.

Кроме того, учитывая то, что вы пытаетесь сделать, я предложил бы предложить другую вспомогательную функцию, has_parent(). Это позволяет подклассам легко проверить, имеет ли данный узел родительский элемент, не предоставляя доступ к внешнему миру.

class Node { 
protected: 
    Node* _parent; 

    static void set_parent(Node* n, Node* parent) { 
    n->_parent = parent; 
    } 

    static bool has_parent(Node* n) { 
    return n->_parent != nullptr; 
    } 
}; 

class Group : public Node { 
protected: 
    std::vector<Node*> _nodes; 
public: 
    void insert(Node* node) { 
    if (has_parent(node)) { 
     throw std::runtime_error("node already has a parent"); 
    } 
    _nodes.push_back(node); 
    set_parent(node, this); 
    } 
}; 

Примечание, однако, что если Group не предназначен быть специальный узел, который управляет другими узлами, то я не рекомендую делать это подкласс Node; скорее, если нет отношения is-a, Node должен объявить Group как friend. Вопреки несколько популярному мнению, это не нарушает инкапсуляцию; скорее, он увеличивается инкапсуляция, так как она позволяет Group управлять Node без предоставления задней двери, что кто-то может использовать, чтобы попасть в Node тоже (любой код можно использовать Accessors & мутаторов, и любой класс, который имеет Node как родитель может доступ к помощникам protected и Node был бы бессилен остановить их, и наоборот, объявление friend позволяет Node разрешить Group для доступа к нему, не отказывая в доступе к остальной вселенной).

И наоборот, если Group действительно является типом Node, то Node должен иметь виртуальный деструктор, поэтому его можно использовать полиморфно. В конце концов, нет никакого способа для Group узнать, какие из его узлов - другие Group s.

int main() { 
    Group g; 

    g.insert(new Node); // Valid. 
    g.insert(new Group); // Valid. Group is just a Node in a fancy suit, right? 
} 
+0

Отлично! Это именно то, что я искал! Это то, что я сейчас использую: http://coliru.stacked-crooked.com/a/03200ca9810d0a8b; 'Group' ** является ** фактически« узлом », я хорошо знаю, что в противном случае было бы бессмысленно иметь его как подкласс« Node ». –

+0

@ I-V Извините, если я пропустил это или неправильно истолковал его в одном из ваших комментариев. Это, конечно, не то же самое, что и ваш ответ. –

+0

@ I-V Приношу свои извинения. Когда вы пишете ответ, я обычно переключаюсь между вкладкой вопросов, одним или несколькими онлайн-компиляторами для тестирования моего ответа и любыми другими вкладками, которые могут потребоваться для исследования и проверки, и может потребоваться некоторое время, чтобы улучшить его, проверить альтернативы и/или убедиться, что ничего не получилось. Вероятно, я пропустил, что вы отправили свой ответ, когда делали это, и не заметили его после того, как я прошел. Виноват. –

0

Вы можете сделать его чище.

void set_value_of(Parent* x, int value) { 
    Child* ch = dynamic_cast<Child*>(x); 
    if (ch != nullptr) 
    { 
     ch->value = value; 
    } 
} 

Он прочен и находится в пределах правил языка.


Основываясь на реальном коде ...

Node, как размещен, это довольно плохо спроектированный класс. Это действительно должно сделать переменную-член parent_ a public или предоставляет функции для получения и установки переменной-члена.

Если у вас есть выбор, изменить его на что-то вроде:

class Node { 
    public: 
    Node(Node* p = nullptr) : _parent(p) {} 
    Node* getParent() const { return _parent; } 
    void setParent(Node* p) { _parent = p; } 

    private: 
    Node* _parent; 
}; 
+0

, как он будет работать, если значение «защищено»? – Ap31

+0

'ch' имеет тип' Child * '. Следовательно, 'Child :: set_value_of' может обращаться к переменной' value' member через 'Child *'. –

+1

Ну, «Родитель» может быть чем угодно, а не просто «Ребёнок» в моем сценарии. Мне еще нужно установить его элемент «Parent :: value»:/ –

0

Что может пойти не так, если вы накладываете й к ребенку? Это случай никогда не делать этого или вы можете, если вы знаете, что делаете?

Много вещей, и именно поэтому вы должны использовать dynamic_cast, а не static_cast.

void set_value_of(Parent* x, int value) { 
    Child * child = dynamic_cast<Child*>(x); 
    if(child) child->value = value; 
} 

dynamic_cast гарантирует, что приведение будет возвращать nullptr если объект на который указывает на это не так, на самом деле, Child объект.

EDIT: основанные на комментариях, это похоже на наследование - неправильный дизайн для этих объектов. Если Child должно иметь прямое агентство по объектам Parent, к которым оно относится, то Child должно быть сделано friend из Parent и не должно происходить от Parent. То, что вы делаете, по своей сути подвержено ошибкам и представляет собой запутанный дизайн.

class Parent { 
    int value; 
    friend class Child; 
}; 

class Child { 
public: 
    void set_value_of(Parent* x, int value) { 
     //Is now valid because Child is a friend of Parent 
     x->value = value; 
    } 
}; 
+0

Я уверен, что идея состоит в том, что 'x' на самом деле не указывает на' Child'. – user2357112

+0

Да, объявление «друга» - это возможность. Тем не менее, подумайте, что есть «модуль расширения» или что-то новое, а код «Родитель» не должен быть изменен, мы не смогли добавить декларацию 'friend' для этого нового подкласса« Parent » –

+0

@NiklasR. Затем вам нужно пересмотреть дизайн вашего кода. Если вам нужен открытый интерфейс для «Родитель», чтобы новые модули могли получить к нему доступ, тогда либо сделайте «Child» этим интерфейсом (например, я сделал в моем примере), либо предоставим этот интерфейс самому «Parent». То, как вы пытаетесь это сделать, только вызовет головную боль по линии. – Xirema

Смежные вопросы