Альтернатива вызову виртуальных / производных методов из конструктора с использованием функции обратного вызова?
У меня есть ситуация, когда я падаю из-за вышеупомянутой проблемы. Я хотел бы построить дерево со связанными узлами. Поскольку все деревья будут вести себя одинаково, но с разными типами, в духе наследования я хотел бы иметь возможность создавать деревья с разными типами, используя одну и ту же процедуру построения дерева, определенную базовым классом. Я хотел бы знать, какова лучшая практика для моей ситуации.
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 ответа
Идея это звук; однако было бы безопаснее создать "собственный конструктор дерева" внутри производного класса, а не извлекать его извне. Таким образом, вы не можете получить неправильный тип узла. В кодовой форме:
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
класс остается нормальным.
Мне трудно понять, зачем вам нужен дизайн (структура внутри производной структуры, полученная из структуры внутри базового класса, выглядит по меньшей мере сомнительной).
При этом вы могли бы рассмотреть обходные пути из часто задаваемых вопросов mclow.
tl; dr заключается в том, что вы перемещаете логику из конструктора в init() и внутри init() вызываете виртуальную функцию "работа".