7

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. 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. 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?

Community
  • 1
  • 1
user3281410
  • 502
  • 3
  • 14
  • So you want non-default behavior from a default copy constructor? Why do you want to copy a container anyway? – orfdorf Jun 14 '14 at 15:55
  • @SchizoidSpag The tree starts off as having 1 node, and then I build a list of trees by adding all of the possible nodes that fit certain criteria to the nodes that are already there. I don't know how to do that without copying the trees and adding new children. – user3281410 Jun 14 '14 at 16:00
  • Is the container really so big and complicated there's lots of data which should be default-copied? – Deduplicator Jun 14 '14 at 16:01
  • Is there a problem with adding to the tree directly, adding its children by reference instead of copying by value, or using move semantics? If you don't need to maintain multiple copies of the tree, you don't need a copy constructor, but a better design. – orfdorf Jun 14 '14 at 16:02
  • @Deduplicator The nodes are complicated, and each subclass is different. The container is not terribly complicated, though it keeps track of a lot of stuff. I just posted the bare bones here so that the problem would be easy to read and would be as widely useful to people that read it as possible. – user3281410 Jun 14 '14 at 16:03
  • @SchizoidSpag The trees represent hierarchical goals for an entity to accomplish. The entity generates a list of all trees that it can think of, and votes on which it likes best as it does that. To generate the trees, it begins with a bunch of 1 node trees, and then it successively adds missing nodes of all possible types to create a big list of distinct trees. I think I need the copy for this. – user3281410 Jun 14 '14 at 16:09

4 Answers4

10

The typical way to solve this problem is to have a virtual clone function similar to what Kerrek SB describes in his answer. However I would not bother writing your own value_ptr class. It is simpler to just reuse std::unique_ptr as your question presents. It will require a non-default copy constructor in AbstractNode, but does not require explicit or unsafe 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 a ConcreteNode can be implemented. It must have a valid copy constructor which may be defaulted depending upon what data members ConcreteNode adds to the mix. And it must implement clone(), but that 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 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.

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. 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:

    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>. 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*.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • I'm still reading through this, but from what I have read so far... First, is the new ConcreteNode(*this) call going to end up calling the AbstractNode copy constructor? If that happens, I think I understand what you're saying here. And second, am I correct that I need to still manually copy all of the members in the AbstractNode class in the copy constructor? Or are you suggesting that if I put all of members from abstract node that I wished would default copy into concrete node, and then used further subclasses of concrete node with their own clone functions, I could avoid that? – user3281410 Jun 14 '14 at 16:47
  • Also, I've been using make_unique all the time. I thought it was part of C++ 11. My Visual Studio Express 2013 compiler seems to let me use it, so if you want to use it in your answer it would be applicable to me, I think. – user3281410 Jun 14 '14 at 16:51
  • I'm not sure that it's good advice to reinvent all those wheels. Users should rarely write destructors, and if so only when absolutely in need. You're introducing raw loops for no reason other than to reinvent the vector's copy constructor. I don't think that's a scalable mentality. – Kerrek SB Jun 14 '14 at 16:54
  • @user3281410: Yes, the `ConcreteNode` copy constructor will implicitly call the `AbstractNode` copy constructor. And yes, the `AbstractNode` copy constructor needs to be user-defined as shown above **unless** you want to write `value_ptr` as described in Kerrek SB's answer. I think it is easier to just write the copy constructor. However ultimately we probably will get some `value_ptr` provided by the standard, and then it will be easier to use that. – Howard Hinnant Jun 14 '14 at 16:54
  • I've upvoted Kerrek SB's answer as that is also a valid approach. As the inventor of `unique_ptr`, I can attest to the fact that writing your own smart pointer, even something as simple as `unique_ptr` and `value_ptr` can be difficult to get right. – Howard Hinnant Jun 14 '14 at 16:57
  • @HowardHinnant Isn't this Prototype design pattern? BTW, I can't believe I ended up designing exactly same solution even before reading this post! I'm quite happy that my design is now supported by expert :) – Chadwick Robbert Apr 04 '16 at 23:41
  • @ChadwickRobbert: Beats me. I have "Design Patterns" on my bookshelf, but it has been long enough since I've read it that I no longer have the names of the patterns memorized. Sounds like I need a refresher course. :-) – Howard Hinnant Apr 05 '16 at 00:49
5

Instead of using unique_ptr, you may wish to roll your own implementation of a value_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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I see how this immediately solves my problem. I'm wary of rolling my own class, since I am still learning the details of C++ and a few of the language elements you are using there are beyond me. Is there a library that has something like this? Also, in my particular case I have a large function that automatically constructs nodes of the correct type. It takes a NodeType enum and has a big switch statement with lines like "case ... : return make_unique(...)". Could I use that to keep the clone function in the abstract class instead of adding it to each subclass node? – user3281410 Jun 14 '14 at 17:07
2

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

#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. Write an implementation based on dynamic_cast instead, and use to_polymorphic(Specialized&& temp, CF functor).

utnapistim
  • 26,809
  • 3
  • 46
  • 82
0

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.

Implementation left as an exercise for the interested reader.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • I'm trying to understand how I would do this; it seems very simple. But the nodes need to access their children all the time, so the sub-object that has the default-constructor is going to need to have access to the children somehow, so it will need some kind of pointer, and I don't see how I can avoid the problem by doing it this way? – user3281410 Jun 14 '14 at 16:33
  • If you use inheritance, you can always use `static_cast` to get to the parent. You might consider using the [CRTP](http://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp) for that, though it's probably extreme overkill. – Deduplicator Jun 14 '14 at 16:35