Альтернатива вызову виртуальных / производных методов из конструктора с помощью функции обратного вызова? - PullRequest
0 голосов
/ 24 августа 2018

У меня есть ситуация, когда я падаю из-за вышеупомянутой проблемы.Я хотел бы построить дерево со связанными узлами.Поскольку все деревья будут вести себя одинаково, но с разными типами, в духе наследования я хотел бы иметь возможность создавать деревья с разными типами, используя одну и ту же процедуру построения дерева, определенную базовым классом.Я хотел бы знать, что лучше всего подходит для моей ситуации.

struct Tree
{
    struct Node { std::list<std::shared_ptr<Node>> children_; };

    Tree()
    {
        root_ = CreateNode();

        // carry on adding nodes to their parents...
    }

    virtual std::shared_ptr<Node> CreateNode() { return std::shared_ptr<Node>(new Node()); }

    std::shared_ptr<Node> root_;
};

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived() : Tree() {}

    virtual std::shared_ptr<Node> CreateNode() { return std::shared_ptr<NodeDerived>(new NodeDerived()); }
};

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

struct Tree
{
    struct Node { std::list<std::shared_ptr<Node>> children_; };

    Tree(std::function<std::shared_ptr<Node>()> customNodeConstructor)
    {
        root_ = customNodeConstructor();

        // carry on adding nodes to their parents... using the customNodeConstructor to create the nodes.
    }

    std::shared_ptr<Node> root_;
};

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived(std::function<std::shared_ptr<Node>()> customNodeConstructor) : Tree(customNodeConstructor) {}
};

, которая позволяет мне выводить и передавать customNodeConstructor, относящийся кпроизводное дерево.Безопасность типов частично обеспечивается, поскольку возвращаемый объект shared_ptr должен быть производным от Tree::Node, хотя не применяется к производному типу узла.

т.е. экземпляр TreeDerived, который, возможно, должен использовать TreeDerived::NodeDerived, принудительно должен использовать только Tree::Node или производный тип, но не обязательно TreeDerived::NodeDerived.

Затем это можно использовать следующим образом ...

Tree tree([]() { return std::shared_ptr<Tree::Node>(); });

TreeDerived treeDerived([]() { return std::shared_ptr<TreeDerived::NodeDerived>(); });

Это хорошая практика или я должен делать больше / что-то еще, не шаблонизируя объект Tree?

Большое спасибо.

Ответы [ 2 ]

0 голосов
/ 27 августа 2018

Мне трудно понять, почему вам нужен дизайн, который вам нужен (структура внутри производной структуры, производная от структуры внутри базового класса, по меньшей мере сомнительна).

При этом можно сказать, что вы могли бы рассмотреть обходные пути из часто задаваемых вопросов mclow.

tl; dr заключается в том, что вы перемещаете логику из конструктора в init (), а внутри виртуальной функции init () вызывается "работа".

0 голосов
/ 24 августа 2018

идея звучит; однако было бы безопаснее создать «собственный конструктор дерева» внутри производного класса, а не извлекать его извне. Таким образом, вы не можете получить неправильный тип узла. В кодовой форме:

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived() : Tree([]() { return std::make_shared<NodeDerived>(); }) {}
};

Также обратите внимание, что в общем случае std::function влечет за собой нетривиальные издержки времени выполнения для каждого вызова. Если вы всегда собираетесь передавать лямбду без сохранения состояния, рассмотрите возможность взятия простого указателя на функцию в конструкторе Tree:

Tree(std::shared_ptr<Node> (*customNodeConstructor)())
{
    root_ = customNodeConstructor();

    // carry on adding nodes to their parents... using the customNodeConstructor to create the nodes.
}

В качестве альтернативы этому подходу "передать в настраиваемом создателе", вы также можете превратить только конструктор Tree в шаблон функции. Тогда это может выглядеть так:

template <class T> struct TypeTag;

struct Tree
{
    struct Node { std::list<std::shared_ptr<Node>> children_; };

    template <class ConcreteNode>
    Tree(TypeTag<ConcreteNode>)
    {
        root_ = std::make_shared<ConcreteNode>();

        // carry on adding nodes to their parents...
    }

    std::shared_ptr<Node> root_;
};

struct TreeDerived : public Tree
{
    struct NodeDerived : public Tree::Node {};

    TreeDerived() : Tree(TypeTag<NodeDerived>{}) {}
};

В этом случае шаблон конструктора должен быть определен где-нибудь, где все классы, производные от Tree, смогут увидеть его определение (что, скорее всего, в заголовочных файлах), но остальная часть класса Tree остается нормальной.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...