简体   繁体   English

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

[英]std::unique_ptr, Default Copy Constructor, and Abstract Class

I have class representing a tree object that uses unique pointers, some nodes that make up the tree, and a function that constructs pointers to an abstract node class based on some arguments (It makes pointers to subclasses, since abstract node is abstract) 我有一个表示一个树对象的类,它使用唯一的指针,构成树的一些节点,以及一个基于某些参数构造指向抽象节点类的指针的函数(它生成指向子类的指针,因为抽象节点是抽象的)

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

There are a variety of subclasses of abstractNode that will be contained in the tree. abstractNode有各种子类,它们将包含在树中。 The subclasses provide different implementations for some virtual functions in that class. 子类为该类中的某些虚函数提供了不同的实现。

I want to be able to copy my tree by creating a new set of nodes with the same class types that are distinct copies of the nodes in the original tree. 我希望能够通过创建一组具有相同类类型的新节点来复制我的树,这些类型是原始树中节点的不同副本。


Here is the problem: 这是问题所在:

If I write my own copy constructor for the AbstractNode class that deep copies the children, I'll have to write copy constructors for all of the subclasses of AbstractNode , which seems annoying since the only thing that won't copy correctly are the children pointers. 如果我为深入复制子项的AbstractNode类编写自己的复制构造函数,我将不得不为AbstractNode所有子类编写复制构造函数,这看起来很烦人,因为唯一不能正确复制的是子指针。 It will also be annoying to use copy constructors here because I will need to cast the children to the correct types before I call them, I think. 在这里使用复制构造函数也很烦人,因为我需要在调用它们之前将它们转换为正确的类型,我想。

Is there some way I can get the compiler to let me use the default copy constructor to set up everything except for the children. 有没有什么方法可以让编译器让我使用默认的复制构造函数来设置除孩子之外的所有东西。 It can leave those as null pointers or something? 它可以将那些作为空指针或其他东西? Then I can write a simpler function that just recursively adds children to copy a tree. 然后我可以编写一个更简单的函数,只是递归地添加子项来复制树。

If that is impossible, is there any non-ugly solution to this problem that anyone knows of? 如果这是不可能的,那么任何人都知道这个问题是否有任何非丑陋的解决方案?

The typical way to solve this problem is to have a virtual clone function similar to what Kerrek SB describes in his answer. 解决此问题的典型方法是使用类似于Kerrek SB在其答案中描述的虚拟clone功能。 However I would not bother writing your own value_ptr class. 但是我不打算写你自己的value_ptr类。 It is simpler to just reuse std::unique_ptr as your question presents. 当你的问题出现时,重用std::unique_ptr更简单。 It will require a non-default copy constructor in AbstractNode , but does not require explicit or unsafe casting: 它将需要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;
};

Now a ConcreteNode can be implemented. 现在可以实现ConcreteNode It must have a valid copy constructor which may be defaulted depending upon what data members ConcreteNode adds to the mix. 它必须具有有效的复制构造函数,可以根据ConcreteNode添加到混合中的成员数据进行默认。 And it must implement clone() , but that implementation is trivial: 它必须实现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));
        }
};

I suggest having clone return a unique_ptr instead of a raw pointer just to ensure that there is no chance that a new'd pointer is ever exposed without an owner. 我建议让clone返回一个unique_ptr而不是一个原始指针,以确保在没有所有者的情况下不会暴露新指针。

For completeness I've also shown what the other special members would look like. 为了完整起见,我还展示了其他特殊成员的样子。

At first I was thinking that C++14's make_unique would be nice to use here. 起初我以为C ++ 14的make_unique在这里使用会很好。 And it can be used here. 它可以在这里使用。 But personally I think in this particular example it really doesn't carry its weight. 但我个人认为,在这个特殊的例子中,它确实没有发挥其重要作用。 Fwiw, here is what it would look like: Fwiw,这就是它的样子:

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

Using make_unique you have to first construct a unique_ptr<ConcreteNode> , and then rely on the implicit conversion from that to unique_ptr<AbstractNode> . 使用make_unique你必须首先构造一个unique_ptr<ConcreteNode> ,然后依赖于从它到unique_ptr<AbstractNode>的隐式转换。 This is correct, and the extra dance probably will get optimized away as soon as inlining is fully enabled. 这是正确的,一旦内联完全启用,额外的舞蹈可能会被优化掉。 But the use of make_unique just seems like an unnecessary obfuscation here when what you really clearly need is a unique_ptr<AbstractNode> constructed with a new'd ConcreteNode* . 但是,当你真正需要的是使用new'd ConcreteNode*构造的unique_ptr<AbstractNode>时,使用make_unique似乎是一种不必要的混淆。

Instead of using unique_ptr , you may wish to roll your own implementation of a value_ptr . 您可能希望滚动自己的value_ptr实现,而不是使用unique_ptr Those designs have been proposed regularly in the past, but until we have a standardized version, either roll your own or find an existing implementation. 这些设计过去曾经定期提出,但在我们有标准化版本之前,要么自己动手,要么找到现有的实施方案。

It would look a bit like this: 它看起来有点像这样:

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

};

You can build your tree from a set of clonable nodes. 您可以从一组可克隆节点构建树。

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); }
    // ...
};

Now your nodes are copyable automatically without the need for you to write any explicit copy constructors. 现在,您的节点可以自动复制,而无需编写任何显式复制构造函数。 All you need to do is to maintain discipline by overriding clone in every derived class. 您需要做的就是通过在每个派生类中重写clone来维护纪律。

The normal pattern for this is a virtual clone method through your hierarchy. 这种情况的正常模式是通过层次结构的虚拟克隆方法。

If that is impossible, is there any non-ugly solution to this problem that anyone knows of? 如果这是不可能的,那么任何人都知道这个问题是否有任何非丑陋的解决方案?

You can also use template instantiation of a clone function based on copy constructors. 您还可以使用基于复制构造函数的克隆函数的模板实例化。 Here's a solution I use in a web server I'm writing (pet project): 这是我在我正在编写的Web服务器中使用的解决方案(宠物项目):

#pragma once

#include <memory>
#include <cassert>
#include <functional>
#include <stdexcept>
#include <vector>

namespace stdex {
    inline namespace details {

        /// @brief Deep copy construct from (Specialized&)*src
        ///
        /// @retval nullptr if src is nullptr
        /// @retval Specialized clone of *src
        ///
        /// @note Undefined behavior src does not point to a Specialized*
        template<typename Base, typename Specialized>
        Base* polymorphic_clone (const Base* src) {
            static_assert(std::is_base_of<Base, Specialized>::value,
                "Specialized is not a specialization of Base");

            if (src == nullptr)
                return nullptr;
            return new Specialized{ static_cast<const Specialized&>(*src) };
        }
    }

    /// @brief polymorphic reference interface over a base class
    ///
    /// Respects polymorphic behavior of class ref.
    /// Instances have deep copy semantics (clone) and
    /// "[const] Base&" interface
    ///
    /// @note Not regular: no trivial way to implement non-intrusive equality
    ///
    /// @note safe to use with standard containers
    template<typename Base>
    class polymorphic final
    {
    public:

        /// Functor capable to convert a Base* to it's specialized type
        /// and clone it (intrusive implementation can be used)
        typedef std::function<Base* (const Base*)>  clone_functor;

        /// @brief construct (takes ownership of ptr)
        template<typename Specialized, typename CloneSpecialized>
        polymorphic(Specialized* ptr, CloneSpecialized functor) noexcept
        : instance_{ptr}, clone_{std::move(functor)}
        {
            static_assert(std::is_base_of<Base, Specialized>::value,
            "Specialized is not a specialization of Base");
            static_assert(
            std::is_constructible<clone_functor, CloneSpecialized>::value,
            "CloneSpecialized is not valid for a clone functor");
        }

        // not implemented: UB cloning in case client provides specialized ptr
        // polymorphic(Base* ptr);

        polymorphic()
        : polymorphic{ nullptr, clone_functor{} }
        {
        }

        polymorphic(polymorphic&&) = default;

        polymorphic(const polymorphic& other)
        : polymorphic{std::move(other.clone())}
        {
        }
        polymorphic& operator=(polymorphic other)
        {
            std::swap(instance_, other.instance_);
            std::swap(clone_, other.clone_);
            return *this;
        }

        ~polymorphic() = default;

        /// @brief Cast to contained type
        /// @pre instance not moved
        /// @pre *this initialized with valid instance
        operator Base&() const
        {
            assert(instance_.get());
            return *instance_.get();
        }

        /// @brief Cast to contained type
        /// @pre instance not moved
        /// @pre *this initialized with valid instance
        operator const Base&() const
        {
            assert(instance_.get());
            return *instance_.get();
        }

    private:
        polymorphic clone() const
        {
            return polymorphic{
                clone_(instance_.get()), clone_functor{clone_}
            };
        }

        std::unique_ptr<Base>   instance_;
        clone_functor           clone_;
    };

    template<typename Base, typename Specialized, typename CF>
    polymorphic<Base> to_polymorphic(Specialized&& temp, CF functor)
    {
        static_assert(std::is_base_of<Base, Specialized>::value,
        "Specialized is not a specialization of Base");

        typedef typename polymorphic<Base>::clone_functor clone_functor;

        auto    ptr_instance = std::unique_ptr<Base>{
            new Specialized{std::move(temp)}
        };
        auto    clone_instance = clone_functor{std::move(functor)};

        return polymorphic<Base>{ptr_instance.release(), clone_instance};
    }

    template<typename Base, typename Specialized>
    polymorphic<Base> to_polymorphic(Specialized&& temp)
    {
        static_assert(std::is_base_of<Base, Specialized>::value,
        "Specialized is not a specialization of Base");

        return to_polymorphic<Base,Specialized>(
            std::move(temp), details::polymorphic_clone<Base,Specialized>
        );
    }

    template<typename Base, typename Specialized, typename ...Args>
    polymorphic<Base> to_polymorphic(Args ...args)
    {
        static_assert(std::is_constructible<Specialized, Args...>::value,
        "Cannot instantiate Specialized from arguments");

        return to_polymorphic<Base,Specialized>(
            std::move(Specialized{std::forward<Args...>(args...)}));
    }

    template<typename Base> using polymorphic_vector =
    std::vector<polymorphic<Base>>;

    template<typename Base, typename ...Args>
    polymorphic_vector<Base> to_polymorphic_vector(Args&& ...args)
    {
        return polymorphic_vector<Base>{to_polymorphic<Base>(args)...};
    }

} // stdex

Example use: 使用示例:

stdex::polymorphic_vector<view> views = // explicit type for clarity
    stdex::to_polymorphic_vector(
        echo_view{"/echo"}, // class echo_view : public view
        directory_view{"/static_files"} // class directory_view : public view
    );

for(auto& v: views)
    if(v.matches(reuqest.url())) // bool view::matches(...);
        auto response = view.handle(request); // virtual view::handle(...) = 0;

Limitations of this: 限制:

If you use multiple inheritance DO NOT USE stdex::details::polymorphic_clone. 如果使用多重继承,请勿使用stdex :: details :: polymorphic_clone。 Write an implementation based on dynamic_cast instead, and use to_polymorphic(Specialized&& temp, CF functor) . 编写一个基于dynamic_cast的实现,并使用to_polymorphic(Specialized&& temp, CF functor)

If you want to use the default behavior for part of your class, and only enhance it with non-standard behavior for the rest, consider functionally and organisationally splitting your class: 如果您想对类的一部分使用默认行为,并且仅针对其余部分使用非标准行为进行增强,请考虑在功能上和组织上分割您的类:

Put all those elements where you want the default-behavior into their own sub-object (inherited or composited), so you can easily use the default-special-function for them, and add the rest outside that sub-object. 将所有这些元素放在您想要默认行为的元素中(继承或合成),这样您就可以轻松地为它们使用default-special-function,并将其余部分添加到该子对象之外。

Implementation left as an exercise for the interested reader. 实施留给感兴趣的读者练习。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM