0

So I have a parent Class which is:

class Node
{
public:
    Node();

    void setParentNode(Node* parent) {this->parentNode = parent;}
    Node* getParentNode() {return this->parentNode;}

    std::vector<Node> getChildNodes(){return this->childNodes;}

    void addChildNode(Node* node);
    void removeNode();

private:
    std::vector<Node*> childNodes;
    Node* parentNode = nullptr;
};

And a child inheriting from that class:

class Cube : public Node
{
public:
    Cube();
};

Now I have another file which has a function that uses the child class:

#include "cube.h"

void addCubes(){
     Cube mainCube;
     for(int i = 0; i < 10; i++){
         Cube c;
         mainCube.addChildNode(c);
      }
}

Problem is that mainCube doesn't see the addChildNode function which the parent has. What is the point of inheriting from another class if the parents functions aren't accessible from another place using the child class?

  • 2
    You probably wanna change `addChildNode(Node node)` to `addChildNode(const Node& node)` in order to avoid the slicing of `Cube` to `Node`. – goodvibration Nov 15 '20 at 06:08
  • 1
    Note that just adjusting the signature of `addChildNode` isn't going to get rid of all the slicing issues this design seems likely to incur. The existence of `std::vector childNodes`, for example. It might be worth reconsidering what relationship, exactly, `Cube`s and `Node`s are supposed to have, and why you've chosen inheritance. – Nathan Pierson Nov 15 '20 at 06:15
  • @NathanPierson has a valid point. Have a look at threads like this: https://stackoverflow.com/questions/56464702/avoiding-object-slicing – Hari Nov 15 '20 at 06:24
  • As others have said, you can call a method of a parent class from the child exactly as you'd call one of the child's own methods - as long as the parent method has visibility of "public" or "protected". – paulsm4 Nov 15 '20 at 06:31

3 Answers3

1

No, the parent classes public functions are callable from the child object. However, the prototype of the function is

void Node::addChildNode(Node node);

So it's taking a Node object & not a Cube object. So your compiler cannot find a function which takes a Cube object & hence the error.

The fix is to use a pointer to Node or reference to Node while declaring/defining the function.

So you function should be

void addChildNode(Node & node);

In which case, the Node object can be passed to the function & the compiler will find it.

Even better would be to have

// if you aren't looking to modify the passed object inside addChildNode
void addChildNode(const Node & node); 

The following is fine & hence the function will work

Cube b;   
Node &a = b;

or

Node * pn = &b;
user93353
  • 13,733
  • 8
  • 60
  • 122
  • Should be `const Node & node` if you wanna replicate the author's intention (which does not include the ability of the function to modify the input object). – goodvibration Nov 15 '20 at 06:11
  • 1
    @goodvibration - yes, true. I am just giving the simplest possible function prototype which will compile – user93353 Nov 15 '20 at 06:13
  • It's simpler, but it is generally wrong, since changing function `addChildNode(Node node)` to `addChildNode(Node& node)` opens up the possibility to fall into a world of hurt. Better give this user a complex correct solution rather than a simple incorrect solution. – goodvibration Nov 15 '20 at 06:15
  • Thanks, but my problem is that it says that addChildNode doesn't exist when I do mainCube.addChildNode; – Pierre-Olivier Leroux Nov 15 '20 at 06:23
  • @Pierre-OlivierLeroux - that's because it cannot find addChildNode which takes Cube. addChildNode which takes a Node & addChildNode which takes a Cube are 2 different functions – user93353 Nov 15 '20 at 06:27
  • 1
    Since `Cube` publically inherits from `Node`, that shouldn't be the problem. The mismatch between `Node*` and `Cube`, however, would cause code to fail to compile. – Nathan Pierson Nov 15 '20 at 06:41
  • @NathanPierson - his original question had the method taking a Node object. After I replied he changed it to take a Node *. – user93353 Nov 15 '20 at 06:52
  • @user93353 Right. But the version that takes a `Node` by value should still take an argument of type `Cube` because `Cube` can be sliced down to a `Node`. It'll exhibit undesired behavior in all likelihood, but it'll compile. – Nathan Pierson Nov 15 '20 at 06:55
  • @user93353, NathanPierson is right. Even with pass by value, it is possible to pass a child object. The object gets sliced. – Hari Nov 15 '20 at 06:56
1

The derived classes should be able to see the addChildNode function if you keep the signature of the function aligns. This is not a big issue. However, there are a few more "serious" issues with your code:

  1. You need to make the base class' destructor virtual to avoid some undefined behaviors.

  2. You have to design the ownership of nodes carefully. I guess you want the Node class to own and manage its children nodes. That means function addChildNode actually takes the ownership of the passed in node object, and it should also be deleted during destruction.

  3. In function addCubes(), there is a loop that keeps calling addChildNode function but passes the local variable Cube c; which will be out of scope and destroyed after the loop. Thus, the parent object mainCube will holds pointers to already destroyed objects, and it will cause a crash.

After fixing all these issues, your code looks like this:

class Node
{
public:
    Node() {};
    virtual ~Node() {
        for(auto n: childNodes) delete n;
    };

    void setParentNode(Node* parent) {this->parentNode = parent;}
    Node* getParentNode() {return this->parentNode;}

    std::vector<Node*> getChildNodes(){return this->childNodes;}

    void addChildNode(Node* node) {
        childNodes.push_back(node);
    };
    void removeNode();

private:
    std::vector<Node*> childNodes;
    Node* parentNode = nullptr;
};

class Cube : public Node
{
public:
    Cube() {};
};

void addCubes(){
    Cube mainCube;
    for(int i = 0; i < 10; i++){
        Cube *c = new Cube();
        mainCube.addChildNode(c);
    }
}

It is preferred to use smart pointers to manage memory, and the code is more elegant and easier to read, and it makes it harder to make mistakes :-).

#include <memory>

class Node
{
public:
    Node() {};
    virtual ~Node() {};

    void setParentNode(Node* parent) {this->parentNode = parent;}
    Node* getParentNode() {return this->parentNode;}

    std::vector<std::shared_ptr<Node>>& getChildNodes(){return this->childNodes;}

    void addChildNode(std::shared_ptr<Node> node) {
        childNodes.push_back(std::move(node));
    };
    void removeNode();

private:
    // childNodes own elements in it, they will be deleted automatically.
    std::vector<std::shared_ptr<Node>> childNodes;
    Node* parentNode = nullptr;
};
class Cube: public Node
{
public:
    Cube() {};
};
void addCubes(){
    Cube mainCube;
    for(int i = 0; i < 10; i++){
        auto c = std::make_unique<Cube>();
        mainCube.addChildNode(std::move(c));
    }
}
Tiger Yu
  • 744
  • 3
  • 5
0

Assuming that you have shared the entire implementation of your code. You have not defined the body of the function addChildNode, similar to setParentNode etc. You need to do something like childNodes.push_back(node); inside that function.

Note: It is also necessary that you pass the input to addChildNode as shown in the answer by @user93353. Also, define childNodes as std::vector<Node *> in order to avoid object slicing.

Hari
  • 1,561
  • 4
  • 17
  • 26