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

时间:2018-08-24 11:32:36

标签: c++ constructor virtual c++17

我遇到上述问题的情况。我想构造一个带有关联节点的树。由于所有树的行为都相同,但类型不同,因此在继承的精神上,我希望能够使用基类定义的相同树构造例程来构造具有不同类型的树。我想知道哪种最佳做法适合我的情况。

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>(); });

这是一种好习惯吗,还是我应该做更多/其他事而不用模板对象?

非常感谢。

2 个答案:

答案 0 :(得分:2)

这个想法是正确的;但是,在派生类中创建“自定义树构造函数”而不是在外部使用它会更安全。这样,您将无法获得错误的节点类型。以代码形式:

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类的其余部分保持正常。

答案 1 :(得分:0)

我很难理解为什么要进行设计(至少可以说,源自基类内部结构的派生内部结构看起来很可疑)。

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

tl; dr是将逻辑从构造函数移到init()中,并且在init()内部的虚函数调用“工作”。