3

I often find myself using unique pointers in C++ when I want polymorphic behaviour. I typically implement pure abstract classes something like the below:

class A { 
public:
    virtual A* clone() const = 0; // returns a pointer to a deep copy of A
    // other methods go here
};

The clone method comes in handy when I want to embellish another class with its own instance of A, for example:

#include <memory>

class B {
private:
    std::unique_ptr<A> a_ptr;
public:
    // ctor
    B(const A& a) {
        a_ptr = std::unique_ptr<A>(a.clone());
        //...
    }
    // copy ctor
    B(const B& other) : B(*other.a_ptr) {}
};

I invariably end up implementing the copy constructor in B to avoid a compiler error (MSVC gives a vague message about attempting to reference a deleted function), which makes complete sense because of the unique pointer. My questions can be summarised as follows:

  1. Do I actually need the copy constructor in B? Perhaps there's a better pattern that would allow me to avoid it altogether.

  2. If yes to 1, can I stop there? Will I ever need to implement the other default functions? I.e. is there any scenario where I need a default constructor and destructor also?

In practice, whenever I feel I need to implement the default functions, I typically implement a move-constructor alongside the other three; I usually use the copy-and-swap-idiom (as per GManNickG's answer in this thread). I assume this wouldn't change anything, but maybe I am wrong!

Thanks a lot!

Community
  • 1
  • 1
  • 5
    The class __owns__ a unique resource. It does not break the rule of 3 (or 5) because the compiler errors. If however you need the functionality that the missing methods provide you have 2 choices: (1) add the methods to each class; (2) write a `my_sort_of_unique_ptr` class that implements a copy constructor that does clone (you might need move as well) and use that instead of `std::unique_ptr` – Richard Critten Jul 13 '18 at 10:15
  • 1
    Thanks Richard; I like your suggestion (2) a lot. I think a customised unique pointer class would be quite an elegant solution. – gilesformiles Jul 13 '18 at 10:40

2 Answers2

3

First, I think the signature of your clone function could be

virtual std::unique_ptr<A> clone() = 0;

as you want deep copies of A instances and exclusive ownership within B. Second, you indeed have to define a copy constructor for your class when you want it to be copyable. Same for an assignment operator. This is due to the fact that std::unique_ptr is a move-only type, which hinders the compiler to generate default implementations.

Other special member functions are not needed, though they might make sense. The compiler won't generate move constructor and move assignment operator for you (as you ship your own copy/assignment functions), though in your case, you can = default; them easily. The destructor can equally well be defined with = default;, which would be in line with the core guidelines.

Note that defining the destructor via = default should be done in a translation unit, as std::unique_ptr requires the full type do be known upon freeing its resource.

Whether you need a default constructor totally depends on how yo want to use the class B.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • Thanks for the explanation lubgr. I was aware of the restrictions on copying and assignment with unique pointers, but not the concept of move-only types. I had never encountered ' = default;' before either, but I can see myself using it a lot. – gilesformiles Jul 13 '18 at 14:09
1

As @lubgr mentioned in his answer, You should return unique_ptr not a raw one from the clone function. Anyway, going to Your questions:

  1. Do You need a copy constructor in B? Well it depends on Your use cases, but if You copy objects of class B You may need one. But as You said, You do it quite often, so it would be wise to consider more generic approach. One of these would be creating a wrapper for unique_ptr which would have copy constructor and which would make a deep copy of this pointer in this copy constructor. Consider following example:

    template<class T>
    class unique_ptr_wrap {
    public:
        unique_ptr_wrap(std::unique_ptr< T > _ptr) : m_ptr(std::move(_ptr)){}
    
        unique_ptr_wrap(const unique_ptr_wrap &_wrap){
            m_ptr = _wrap->clone();
        }
    
        unique_ptr_wrap(unique_ptr_wrap &&_wrap){
            m_ptr = std::move(_wrap.m_ptr);
        }
    
        T *operator->() const {
            return m_ptr.get();
        }
    
        T &operator*() const {
            return *m_ptr;
        }
    private:
        std::unique_ptr< T > m_ptr;
    };
    
  2. This again depends on Your needs. I personally would recommend overloading move constructor as well, to make it use less dynamic allocations (but this may be premateure optimization which is root of all evil).
bartop
  • 9,971
  • 1
  • 23
  • 54
  • Thanks bartop. Of course you are right that whether I need a copy constructor for B depends on whether it's actually used! In my case most classes will (hopefully) be used by other people, so I will assume so. I think your answer is very close to what I will implement: I will add an assignment operator and a base class for clonable types (and ensure T is derived from the clonable base class). – gilesformiles Jul 13 '18 at 13:57