Std :: unique_ptr, default copy constructor and abstract class

I have a class representing a tree object that uses unique pointers, some nodes that make up the tree, and a function that builds pointers to an abstract class node 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 many subclasses of abstractNode that will be contained in the tree. Subclasses provide various implementations for some of the virtual functions of this class.

I want to be able to copy my tree by creating a new set of nodes with the same types of classes that are separate copies of the nodes in the source tree.


Here is the problem:

If I write my own copy constructor for the AbstractNode class, which deeply copies children, I will have to write copy constructors for all AbstractNode subclasses, which seems annoying since the only thing that won is t copy are pointers to children. It will also be frustrating for me to use copy constructors, because I will need to drop the children to the correct types before I call them, I think.

Is there a way to force the compiler to allow me to use the default copy constructor to configure everything except children. Can it leave them as null pointers or something else? Then I can write a simpler function that just recursively adds children to copy the tree.

If this is not possible, is there any non-ugly solution to this problem that anyone knows about?

+6
source share
4 answers

A typical way to solve this problem is to have a virtual clone function similar to the one described by Kerrek SB in its answer. However, I would not write my own class value_ptr . It’s easier to just reuse std::unique_ptr as you ask your question. It will require a non-default copy constructor in AbstractNode , but does not require explicit or insecure casting:

 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 you can run ConcreteNode . It must have a valid copy constructor, which can be defaulted depending on which ConcreteNode data elements are added to the mix. And it should implement clone() , but the implementation is trivial:

 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 that clone return unique_ptr instead of a raw pointer, to make sure there is no way that the new'd pointer could be opened without an owner.

For completeness, I also showed you what other special members look like.

At first I thought C ++ 14 make_unique would be nice to use here. And it can be used here. But personally, I think that in this particular example he really does not bear his weight. Fwiw, this is what it will look like:

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

Using make_unique , you must first build unique_ptr<ConcreteNode> and then rely on the implicit conversion from this to unique_ptr<AbstractNode> . That's right, and the extra dance is likely to be optimized as soon as the inlay is fully turned on. But using make_unique just seems like unnecessary confusion here, when what you really need is unique_ptr<AbstractNode> built with new'd ConcreteNode* .

+9
source

Instead of using unique_ptr you may need to run your own implementation of value_ptr . These projects have been proposed regularly in the past, but until we have a standardized version, roll up your own or find an existing implementation.

It will look something 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 cloned 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 automatically copied, without having to write any explicit copy constructors. All you have to do is maintain discipline by overriding clone in each derived class.

+4
source

The usual template for this is the virtual cloning method through your hierarchy.

If this is not possible, is there any non-ugly solution to this problem that anyone knows about?

You can also use an instance of the clone function template based on copy constructors. Here is the solution I am using on the web server that I am writing (pet project):

 #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 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 

Usage example:

 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:

If you are using multiple inheritance, DO NOT use stdex :: details :: polymorphic_clone. Instead, write an implementation based on dynamic_cast and use to_polymorphic(Specialized&& temp, CF functor) .

+1
source

If you want to use the default behavior for part of your class and just improve it with custom behavior for the rest, consider the functional and organizational separation of your class:

Put all those elements in which you want the default behavior to be in their own sub-object (inherited or composed), so you can easily use the special default function for them and add the rest outside this sub-object.

The implementation is left as an exercise for the interested reader.

0
source

Source: https://habr.com/ru/post/970841/


All Articles