使用回调函数从构造函数调用虚拟/派生方法的替代方法?

Alternative to calling virtual/derived methods from constructor using callback function?

本文关键字:方法 派生 虚拟 函数调用 回调 函数      更新时间:2023-10-16

我遇到上述问题的情况。我想构建一个带有关联节点的树。由于所有树的行为都相同,但类型不同,本着继承的精神,我希望能够使用基类定义的相同树构造例程来构造不同类型的树。我想知道针对我的情况的最佳实践是什么。

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方法的基本实现,该方法始终使用基节点实现构造树。这意味着我可以在不延迟树木数量的情况下构建树木。显而易见的解决方案是模板树以采用不同的节点类型,使用特征强制执行节点类型?但是,这也意味着所有方法的定义都必须在标头中?这门课很肉,所以如果可能的话,我想避免这种情况,所以我考虑传递一个为我做这件事的 lambda。

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 对象的情况下做更多/其他事情?

非常感谢。

这个想法是合理的;但是,在派生类中创建"自定义树构造函数"比在外部使用它更安全。这样,您就不会获得不正确的节点类型。在代码形式中:

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

另请注意,在一般情况下,std::function每次调用都会产生不平凡的运行时开销。如果您总是要传入无状态 lambda,请考虑在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(( 中调用虚拟函数"work"。