繁体   English   中英

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

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

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

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

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

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


这是问题所在:

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

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

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

解决此问题的典型方法是使用类似于Kerrek SB在其答案中描述的虚拟clone功能。 但是我不打算写你自己的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;
};

现在可以实现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));
        }
};

我建议让clone返回一个unique_ptr而不是一个原始指针,以确保在没有所有者的情况下不会暴露新指针。

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

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

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

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

您可能希望滚动自己的value_ptr实现,而不是使用unique_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;
    }

};

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

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

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

这种情况的正常模式是通过层次结构的虚拟克隆方法。

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

您还可以使用基于复制构造函数的克隆函数的模板实例化。 这是我在我正在编写的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

使用示例:

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;

限制:

如果使用多重继承,请勿使用stdex :: details :: polymorphic_clone。 编写一个基于dynamic_cast的实现,并使用to_polymorphic(Specialized&& temp, CF functor)

如果您想对类的一部分使用默认行为,并且仅针对其余部分使用非标准行为进行增强,请考虑在功能上和组织上分割您的类:

将所有这些元素放在您想要默认行为的元素中(继承或合成),这样您就可以轻松地为它们使用default-special-function,并将其余部分添加到该子对象之外。

实施留给感兴趣的读者练习。

暂无
暂无

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

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