std :: unique_ptr,默认复制构造函数和抽象类

use*_*410 7 c++ c++11

我有一个表示一个树对象的类,它使用唯一的指针,构成树的一些节点,以及一个基于某些参数构造指向抽象节点类的指针的函数(它生成指向子类的指针,因为抽象节点是抽象的)

class AbstractNode
{
    vector<unique_ptr<AbstractNode>> children;
public:
    AbstractNode(arguments...);
    // other stuff...
};
class Tree
{
    unique_ptr<AbstractNode> baseNode;
    // other stuff...
}
unique_ptr<AbstractNode> constructNode(AbstractNodeTypes type);
Run Code Online (Sandbox Code Playgroud)

abstractNode有各种子类,它们将包含在树中.子类为该类中的某些虚函数提供了不同的实现.

我希望能够通过创建一组具有相同类类型的新节点来复制我的树,这些类型是原始树中节点的不同副本.


这是问题所在:

如果我为AbstractNode深度复制子类的类编写自己的复制构造函数,我将不得不为所有子类编写复制构造函数AbstractNode,这看起来很烦人,因为唯一不能正确复制的是子指针.在这里使用复制构造函数也很烦人,因为我需要在调用它们之前将它们转换为正确的类型,我想.

有没有什么方法可以让编译器让我使用默认的复制构造函数来设置除孩子之外的所有东西.它可以将那些作为空指针或其他东西?然后我可以编写一个更简单的函数,只是递归地添加子项来复制树.

如果这是不可能的,那么任何人都知道这个问题是否有任何非丑陋的解决方案?

How*_*ant 10

解决此问题的典型方法是使用clone类似于Kerrek SB在其答案中描述的虚函数.但是我不打算写自己的value_ptr课.只需重复使用std::unique_ptr就可以了解问题.它将需要非默认的复制构造函数AbstractNode,但不需要显式或不安全的转换:

class AbstractNode
{
    std::vector<std::unique_ptr<AbstractNode>> children;
public:
    AbstractNode() = default;
    virtual ~AbstractNode() = default;
    AbstractNode(AbstractNode const& an)
    {
        children.reserve(an.children.size());
        for (auto const& child : an.children)
            children.push_back(child->clone());
    }

    AbstractNode& operator=(AbstractNode const& an)
    {
        if (this != &an)
        {
            children.clear();
            children.reserve(an.children.size());
            for (auto const& child : an.children)
                children.push_back(child->clone());
        }
        return *this;
    }

    AbstractNode(AbstractNode&&) = default;
    AbstractNode& operator=(AbstractNode&&) = default;
    // other stuff...

    virtual
        std::unique_ptr<AbstractNode>
        clone() const = 0;
};
Run Code Online (Sandbox Code Playgroud)

现在ConcreteNode可以实现了.它必须具有有效的复制构造函数,可以根据成员ConcreteNode添加到混合中的数据进行默认.它必须实现clone(),但实现是微不足道的:

class ConcreteNode
    : public AbstractNode
{
public:
    ConcreteNode() = default;
    virtual ~ConcreteNode() = default;
    ConcreteNode(ConcreteNode const&) = default;
    ConcreteNode& operator=(ConcreteNode const&) = default;
    ConcreteNode(ConcreteNode&&) = default;
    ConcreteNode& operator=(ConcreteNode&&) = default;
    // other stuff...

    virtual
        std::unique_ptr<AbstractNode>
        clone() const override
        {
            return std::unique_ptr<AbstractNode>(new ConcreteNode(*this));
        }
};
Run Code Online (Sandbox Code Playgroud)

我建议只clone返回一个unique_ptr而不是一个原始指针,以确保没有所有者就没有新的指针暴露出来.

为了完整起见,我还展示了其他特殊成员的样子.

起初我以为make_unique在这里使用C++ 14 会很好.它可以在这里使用.但我个人认为,在这个特殊的例子中,它确实没有发挥其重要作用.Fwiw,这就是它的样子:

    virtual
        std::unique_ptr<AbstractNode>
        clone() const override
        {
            return std::make_unique<ConcreteNode>(*this);
        }
Run Code Online (Sandbox Code Playgroud)

使用make_unique你必须首先构造一个unique_ptr<ConcreteNode>,然后依靠从那里的隐式转换unique_ptr<AbstractNode>.这是正确的,一旦内联完全启用,额外的舞蹈可能会被优化掉.但是make_unique当你真正需要的是一个unique_ptr<AbstractNode>用new'd构建的时候,使用它似乎是一种不必要的混淆ConcreteNode*.


Ker*_* SB 5

而不是使用unique_ptr,你可能希望推出自己的实现value_ptr.这些设计过去曾经定期提出,但在我们有标准化版本之前,要么自己动手,要么找到现有的实施方案.

它看起来有点像这样:

template <typename T> struct value_ptr
{
    T * ptr;
    // provide access interface...

    explicit value_ptr(T * p) noexcept : ptr(p) {}

    ~value_ptr() { delete ptr; }

    value_ptr(value_ptr && rhs) noexcept : ptr(rhs.ptr)
    { rhs.ptr = nullptr; }

    value_ptr(value_ptr const & rhs) : ptr(rhs.clone()) {}

    value_ptr & operator=(value_ptr && rhs) noexcept
    {
        if (&rhs != this) { delete ptr; ptr = rhs.ptr; rhs.ptr = nullptr; }
        return *this;
    }

    value_ptr & operator=(value_ptr const & rhs)
    {
        if (&rhs != this) { T * p = rhs.clone(); delete ptr; ptr = p; }
        return *this;
    }

};
Run Code Online (Sandbox Code Playgroud)

您可以从一组可克隆节点构建树.

struct AbstractNode
{
    virtual ~AbstractNode() {}
    virtual AbstractNode * clone() const = 0;

    std::vector<value_ptr<AbstractNode>> children;
};

struct FooNode : AbstractNode
{
    virtual FooNode * clone() const override { return new FooNode(this); }
    // ...
};
Run Code Online (Sandbox Code Playgroud)

现在,您的节点可以自动复制,而无需编写任何显式复制构造函数.您需要做的就是通过覆盖clone每个派生类来维持纪律.