0

The following code would not compile because apparently that's how protected members work. (There are questions answering the why on SO, eg. here).

class Parent {
protected:
    int value;
};

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

int main() {
}

For example, GCC reports

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;
            ^~~~~

What could possibly go wrong if you cast x to Child? Is it a case of never ever do it or you can, if you know what you're doing?

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


Edit Unfortunately all suggestions for getting around this abuse so far do not help me in solving my design problem, thus I try to be less generic and show my actualy real-world case.

The main goal is to not rely on friend declaration, as the Node class should not need to be modified (ie. no friend declaration should need to be added), to implement another subclass like 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;
  }
};

This could be solved by add friend class Group to the Node class, but I am looking for a way around it.

Community
  • 1
  • 1
Niklas R
  • 16,299
  • 28
  • 108
  • 203
  • What are you trying to do?? Why one class has a method that works on another class? – I-V Dec 09 '16 at 17:38
  • I have linked that very question this should be a duplicate of.. I figured it's not the same question, duh – Niklas R Dec 09 '16 at 17:38
  • What you're doing is valid if you do a `dynamic_cast` instead, and compare the resulting pointer against `nullptr` first. `dynamic_cast` will ensure that it only returns a valid pointer if the casted object is, indeed, a pointer of that type. – Xirema Dec 09 '16 at 17:39
  • In my experience, in c++ everything is "you can, if you know what you're doing" :D I think, this cast would only affect high-level behavior, e.g. you can break some class contract – Ap31 Dec 09 '16 at 17:39
  • @Xirema is right but it will work only for cases like that... in more complex classes you might lise part of the object's data – I-V Dec 09 '16 at 17:40
  • @I-V As the example suggests, I have a child-parent relationship. The real case is that the child is a container for instances of the parent class, and when an element is inserted into the container, the reference to this container should be stored in the instance of the parent class. – Niklas R Dec 09 '16 at 17:42
  • So a better way to implement it would be to export a function (setter) from the partent, and call it from the child instance – I-V Dec 09 '16 at 17:43
  • @I-V But I don't want others be able to call this setter method. Only subclasses should be able because they are in a contract with their `Parent` class to establish the parent-child connection correctly. – Niklas R Dec 09 '16 at 17:45
  • "the child is a container for instances of the parent class" - quick check: why is the child a child of the parent, then? If it was because you were hoping that would give it access to `x`, or because you thought that inheritance was an appropriate way to express that containment relationship, then you probably shouldn't have Child inherit from Parent at all. – user2357112 Dec 09 '16 at 17:46
  • I'm sorry but it is a bad habbit and wrong... you should not access a private member of another class. If you have a good reason then you better use a setter. If a setter is not the solution then fhe design is wrong. So again- what are you willing to do? – I-V Dec 09 '16 at 17:47
  • Bad habits notwithstanding, one way to make an exception to allow the Child class full access would be to declare the Child class to be a friend of the Parent class (i.e. put "friend class Child;" in the Parent class's declaration). – Jeremy Friesner Dec 09 '16 at 17:48
  • @user2357112 The wording is confusing: Parent is an interface, and Child implements it. Though the Child implements it in a way that it contains other instances of the Parent. Thus it is a Parent-Child relationship in terms of polymorphism, but the other way around "during the runtime" so to speak... – Niklas R Dec 09 '16 at 17:55
  • I've updated the question with a stripped down version of the real world use case. That should help you understand the design that I aim for. – Niklas R Dec 09 '16 at 17:56
  • @JeremyFriesner Contrary to popular belief, [use of the `friend` keyword is actually considered to be very good practice](https://isocpp.org/wiki/faq/friends#friends-and-encap) – Xirema Dec 09 '16 at 17:57
  • I still don't see why you inherit from Node... and the implementation you gave, it just make me recommand you (again) to use a setter (and getter) in the Node class – I-V Dec 09 '16 at 18:30
  • @I-V I inherit from Node because maybe I want to put a Group into a Group – Niklas R Dec 09 '16 at 19:43
  • "Maybe" is the key word. then use templates instead of this code – I-V Dec 09 '16 at 21:25

3 Answers3

1

Note that, considering your actual code, I would suggest refactoring if Group isn't intended to be a special type of Node. Please see my note at the bottom of this answer.


If you can modify Parent, one way to do this would be to provide a protected member function as a helper; considering that set_value_of() takes an instance and a value, it should probably be a static one.

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';
}

With this, the output will be 42, 3, as expected.


Considering your actual code, this can still be implemented. The issue is that Node doesn't provide a way for outside influences to safely modify it, which by extension means that it doesn't provide a way for Group to safely modify it. (While Group can indeed try to cast Node* to Group*, that could very well go wrong, and it would likely confuse anyone performing maintenance in the future.) Therefore, if Node is intended to be a base class, and leave actually managing the various nodes to subclasses, it should provide a way for them to tell it what to do.

Additionally, considering what you're trying to do, I would suggest providing another helper function, has_parent(). This allows subclasses to easily check whether a given node has a parent, without providing access to the outside world.

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);
  }
};

Note, however, that unless Group is intended to be a special node which manages other nodes, then I don't recommend making it a subclass of Node; rather, if there's no is-a relationship, Node should declare Group as a friend. Contrary to somewhat-popular belief, this doesn't break encapsulation; rather, it increases encapsulation, because it enables Group to manage Node without providing a back door that anyone else can use to get into Node, too (any code can use accessors & mutators, and any class that has Node as a parent can access protected helpers, and Node would be powerless to stop them; conversely, a friend declaration lets Node allow Group to access it, while denying access to the rest of the universe).

Conversely, if Group really is a type of Node, then Node should have a virtual destructor, so it can be used polymorphically. After all, there's no way for Group to know which of its nodes are other Groups.

int main() {
    Group g;

    g.insert(new Node);  // Valid.
    g.insert(new Group); // Valid.  Group is just a Node in a fancy suit, right?
}
  • Perfect! That is exactly what I was looking for! This is what I'm using now: http://coliru.stacked-crooked.com/a/03200ca9810d0a8b ; `Group` **is** in fact a `Node`, I am well aware that otherwise it would be nonsensical to have it as a subclass of `Node`. – Niklas R Dec 09 '16 at 21:15
  • @I-V I am sorry if I missed it from or misinterpreted it in one of your comments. It's certainly not the same as your answer though. – Niklas R Dec 09 '16 at 21:52
  • @I-V My apologies, then. When writing up an answer, I usually switch between the question tab, one or more online compilers for testing my answer, and any other tabs I may want for research & double-checking stuff, and it can take me a while to refine it, check alternatives, and/or make sure nothing goes wrong. I probably missed you posting your answer while doing this, and didn't notice it after mine went through. My bad. – Justin Time - Reinstate Monica Dec 09 '16 at 22:26
  • @NiklasR Ah, okay, just wanted to make sure. – Justin Time - Reinstate Monica Dec 09 '16 at 22:29
0

You can make it cleaner.

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

It is robust and stays within the rules of the language.


Based on the real code ...

Node, as posted, is a rather poorly designed class. It really should make parent_ a public member variable or provides functions to get and set the member variable.

If you have option, change it to something like:

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

  private:
    Node* _parent;
};
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • how is it going to work though if the value is `protected`? – Ap31 Dec 09 '16 at 17:42
  • `ch` is of type `Child*`. Hence, `Child::set_value_of` can access the `value` member variable through a `Child*`. – R Sahu Dec 09 '16 at 17:43
  • 1
    Well, `Parent` could be anything, not just a `Child` in my scenario. I still need to set its `Parent::value` member :/ – Niklas R Dec 09 '16 at 17:43
  • @NiklasR, in that case, you can throw an exception, print an error message, or do something that makes sense in the context of your application. Change the value only if the `dynamic_cast` succeeds. – R Sahu Dec 09 '16 at 17:45
  • I'll clarify what I am trying to achieve. I thought I could generalize it, but all suggestions don't lead me to a solution to the design problem I am trying to solve. – Niklas R Dec 09 '16 at 17:48
  • @NiklasR, It sounds like you have run into [the XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – R Sahu Dec 09 '16 at 17:52
  • @RSahu Very much, yes. Though I was certainly curious about the implications to my "solution". – Niklas R Dec 09 '16 at 17:54
  • *It really should make parent_ a public member variable or provides functions to get and set the member variable.* -- But then anyone could change the `_parent` and destroy the correct structure. Isn't that exactly why there are private and protected modifiers, to avoid invalid use of object members.? Otherwise I could just make everything public and be done with it – Niklas R Dec 09 '16 at 18:01
  • @NiklasR, yes they could. You cannot stop determined developers from subverting your intent. You just have to let them shoot themselves in the foot. – R Sahu Dec 09 '16 at 18:04
0

What could possibly go wrong if you cast x to Child? Is it a case of never ever do it or you can, if you know what you're doing?

Lots of things, which is why you should be using a dynamic_cast, not a static_cast.

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

dynamic_cast will ensure that the cast will return nullptr if the object being pointed to is not, in fact, a Child object.

EDIT: based the comments taking place, it sounds like inheritance is the wrong design for these objects. If Child should have direct agency over the Parent objects it refers to, then Child should be made a friend of Parent, and should not derive from Parent at all. What you're doing is inherently error-prone and represents confusing design.

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;
    }
};
Xirema
  • 19,889
  • 4
  • 32
  • 68
  • I'm pretty sure the idea is that `x` does not actually point to a `Child`. – user2357112 Dec 09 '16 at 17:43
  • Yes, declaring a `friend` is a possiblity. Yet, consider there's an "extension module" or something that is new and the code of `Parent` should not be changed, we could not add the `friend` declaration for that new `Parent` subclass – Niklas R Dec 09 '16 at 17:46
  • @NiklasR Then you need to reconsider the design of your code. If you need an open-ended interface for `Parent` such that new modules can access it, then either make `Child` that interface (like I did in my example), or give that interface to `Parent` itself. The way you're trying to do it is only going to cause headaches down the line. – Xirema Dec 09 '16 at 17:49
  • @NiklasR: Why not just provide a public setter, then? If you want any new code that wants access to this variable to be able to get it, that sounds public to me. – user2357112 Dec 09 '16 at 17:50