替代使用回调函数从构造函数调用 virtual/derived 方法?

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

我遇到了上述问题。我想构建一棵具有关联节点的树。由于所有树的行为都相同但类型不同,本着继承的精神,我希望能够使用由基 class 定义的相同树构造例程构造具有不同类型的树。我想知道适合我的情况的最佳做法是什么。

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 方法的基实现,该方法始终使用基节点实现构造树。这意味着我可以在不延迟树木数量的情况下建造树木。显而易见的解决方案是对树进行模板化以采用不同的节点类型,使用特征强制节点类型?但是,这也意味着所有方法的定义都必须在 header? class 非常多,所以我想尽可能避免这种情况,所以我考虑传递一个 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 object 必须派生自 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>(); });

这是好的做法还是我应该做 more/something 而不是模板化树 object?

非常感谢。

想法不错;但是,在派生的 class 中创建 "custom tree constructor" 而不是在外部创建它会更安全。这样,您就不会得到不正确的节点类型。代码形式:

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.
}

作为此 "pass in a customised creator" 方法的替代方法,您还可以仅将 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 派生的 classes 都可以看到它的定义(很可能在头文件中),但是 Tree class 保持正常。

我很难理解你为什么需要你做的设计(struct inside derived struct 派生自 struct inside base class 至少可以说是有问题的)。

话虽如此,您可以考虑 workarounds 来自 mclow 的常见问题解答。

tl;dr 是您将逻辑从构造函数移动到 init() 和 init() 虚函数调用内部 "work".