Наследование от внутреннего класса - PullRequest
1 голос
/ 17 июня 2020

Я реализовал двоичное дерево поиска на C ++ и теперь пытаюсь наследовать от него.

Базовое дерево:

#ifndef TREE_HPP
#define TREE_HPP

class tree {
public:
    class node {
    public:
        virtual int foo() { return 1; }

        node() = default;
        virtual ~node() = default;
    };
    node* root;
    tree() : root(new node) {}
    virtual ~tree() { delete root; }
};

#endif

Производное дерево:

#ifndef DERIVED_TREE_HPP
#define DERIVED_TREE_HPP

#include "tree.hpp"

class derivedTree : public tree {
public:
    class derivedNode : public node {
    public:
        virtual int foo() { return 2; }
    };
};
#endif

Main:

#include "derivedTree.hpp"
#include "tree.hpp"
#include <iostream>
using std::cout;

int main() {
    tree t1;
    derivedTree t2;
    cout << "Hey! " << t1.root->foo() << "\n";
    cout << "Hey! " << t2.root->foo() << "\n";
}

Результат:

Hey! 1
Hey! 1

Я ожидаю, что это будет 1 и 2. Я думаю, это потому, что root - указатель на базу tree, и поэтому вызывает tree::foo(). Как я могу наследовать дерево, чтобы оно содержало производные узлы?

Ответы [ 3 ]

2 голосов
/ 17 июня 2020

Проблема не в наследовании, а в вашем объекте tree, который создает экземпляр объекта node, а не ваш derivedNode.

Я бы использовал для этого шаблоны, что-то вроде этих строк:

template <typename NodeType>
class treeT {
public:

    NodeType* root;
    tree() : root(new NodeType) {}
    virtual ~tree() { delete root; }
};

class node {
public:
    virtual int foo() { return 1; }
    node() = default;
    virtual ~node() = default;
};

class derivedNode : public node {
public:
    virtual int foo() { return 2; }
};

using tree = treeT<node>;
using derivedTree = treeT<derivedNode>;

Если вам действительно нужен внутренний класс, вы можете использовать своего рода CRTP, но я предполагаю, что это будет чрезмерной инженерией.

Обратите внимание, что @songyuanyao также имеет хорошее решение для этот. Я не могу дать вам обоснование выбора того или иного решения.

2 голосов
/ 17 июня 2020

Добро пожаловать в StackOverflow!

Что делают виртуальные методы, так это позволяют вам вызывать свой метод на основе фактического экземпляра класса, а не типа указателя.

Итак, в вашем случае root->foo() вызовет метод, основанный на фактическом классе, и не всегда вызовет реализацию node.

Однако, чтобы вызвать реализацию derivedNode, вы должны создать его экземпляр! Как и сейчас, ваш derivedTree использует базовый конструктор tree, который напрямую создает экземпляр node, поэтому оба derivedTree и tree будут иметь объект node как root!

Чтобы решить проблему, как уже показано в других ответах, вы можете добавить к tree конструктор, который принимает внешний node указатель, и использовать этот конструктор в конструкторе derivedTree для инициализации root с указателем на derivedTree.

Вот так: ( исполняемая ссылка )

class tree {
public:
    class node {
    public:
        virtual int foo() { return 1; }

        node() = default;
        virtual ~node() = default;
    };
    node* root;
    tree(): root(new node) {};
    tree(node* d) : root(d) {}; // here we initialize the root pointer with a given pointer
    virtual ~tree() { delete root; };
};

class derivedTree : public tree {
public:
    class derivedNode : public node {
    public:
        virtual int foo() { return 2; }
    };
    derivedTree(): tree(new derivedNode) {}; // here we use the added constructor to create a derivedNode and set it as root
};

using std::cout;

int main() {
    tree t1;
    derivedTree t2;
    cout << "Hey! " << t1.root->foo() << "\n";
    cout << "Hey! " << t2.root->foo() << "\n";
}

Обратите внимание, что невозможно инициализировать root непосредственно в производный конструктор, потому что язык позволяет помещать в список инициализаторов только поля фактического класса, а не производные поля, и в зависимости от компилятора вы рискуете создать утечку памяти.

2 голосов
/ 17 июня 2020

Вы можете добавить еще один конструктор, принимающий указатель типа node* для tree, и инициализировать root параметром. Затем вы можете передать соответствующий указатель. Например,

class tree {
public:
    ...
    tree(node* r) : root(r) {}
};

class derivedTree : public tree {
public:
    ...
    derivedTree() : tree(new derivedNode) {}
};

ЖИВОЙ

...