c++polymorphismsmart-pointers

Can an object that owns a unique pointer be unique pointed to?


I have the following minimal reproducible example where it seems that my unique-pointer-containing object B is not allowed to contain a unique pointer to another instance of B:

#include <iostream>
#include <memory>

struct C {
  virtual void say_name() = 0;
};

struct A : public C {
  int value;
  A(int value) : value(value) {}
  void say_name() override { std::cout << "A" << value << std::endl; }
};

struct B : public C {
  std::unique_ptr<C> ptr;
  B(std::unique_ptr<C> ptr) : ptr(std::move(ptr)) {}
  void say_name() override { std::cout << "B" << std::endl; }
};

int main() {
  A a(5);
  B b(std::make_unique<A>(10));
  // Here I create another B - causes error
  B b2(std::make_unique<B>(b));
}

I expected the following to happen:

I expected b to own a, and b2 to own b which owns a.

However, I get an error message when I try and create b2 that says I have a call to an implicitly deleted copy-constructor of B.

I don't understand the error or how to fix it and want to understand how I should restructure my program so that I can nest objects in this way.

Here is the full error message:

In template: call to implicitly-deleted copy constructor of 'B'clang(ovl_deleted_special_init)
unique_ptr.h(767, 30): Error occurred here
main.cpp(33, 13): In instantiation of function template specialization 'std::make_unique<B, B &, 0>' requested here
main.cpp(24, 22): Copy constructor of 'B' is implicitly deleted because field 'ptr' has a deleted copy constructor
unique_ptr.h(221, 55): Copy constructor is implicitly deleted because 'unique_ptr<C>' has a user-declared move constructor

Solution

  • Problem is this expression:

    std::make_unique<B>(b)
    

    It attempts to create a copy of b.

    Since B contains uniqe_ptr B is not copyable it is only moveable. So one way to fix it is to move b.

    B b2(std::make_unique<B>(std::move(b)));
    

    https://godbolt.org/z/qGGe1qv8Y

    Other way to approach this it to provide own version of copy constructor. Problem is to archive this you need a way to duplicate ptr member variable. This can be done by adding functionality to C called clone:

    #include <iostream>
    #include <memory>
    
    struct C {
        virtual void say_name() = 0;
        virtual std::unique_ptr<C> clone() const = 0;
        
        virtual ~C() = default;
    };
    
    struct A : public C {
        int value;
        A(int value)
            : value(value)
        {
        }
        void say_name() override { std::cout << "A" << value << std::endl; }
    
        std::unique_ptr<C> clone() const override {
            return std::make_unique<A>(value);
        }
    };
    
    struct B : public C {
        std::unique_ptr<C> ptr;
        B(std::unique_ptr<C> ptr)
            : ptr(std::move(ptr))
        {
        }
        B(const B& other) : ptr{other.ptr->clone()} {
        }
        void say_name() override { std::cout << "B" << std::endl; }
        std::unique_ptr<C> clone() const override {
            return std::make_unique<B>(*this);
        }
    };
    
    int main()
    {
        A a(5);
        B b(std::make_unique<A>(10));
        B b2(std::make_unique<B>(b));
    }
    

    https://godbolt.org/z/eM663Yf1z

    Depending on what you wish to achieve you have to select proper solution.