Search code examples
c++inheritancesmart-pointersunique-ptr

Convert std::unique_ptr of derived to base and pass ownership as argument


I have this code:

#include <iostream>
#include <memory>

class Base
{
public:
    virtual void doSmth() = 0;
};

class Der1 : Base
{
public:
    void doSmth() final
    {
        std::cout << "Hello from der1\n";
    }
};

class Der2 : Base
{
public:
    void doSmth() final
    {
        std::cout << "Hello from der2\n";
    }
};

class Owner
{
public:
    Owner(std::unique_ptr<Base> passedptr)
    {
        ptr = std::move(passedptr);
    }

    std::unique_ptr<Base> ptr;
};

int main()
{
    auto der = std::make_unique<Der1>();
    Owner owner(std::move(der)); // Fails, need to convert
    owner.ptr->doSmth();
}

I want the Owner class to take a base pointer in constructor and set its unique_ptr member to it. I understand that I need to pass it with std::move(), but I don't understand how I can convert it to the base class.

I've seen unique_ptr to a derived class as an argument to a function that takes a unique_ptr to a base class, but solutions from it don't seem to work, I can't just move a pointer to a derived class like I do it in my code, and I can't create a base pointer like this:

std::unique_ptr<Base> der(new Der1());

How can I convert and pass ownership properly?


Solution

  • How can I convert and pass ownership properly?

    There are a few steps you need to do, to make your code correct.

    1. The derived classes should be inheriting the Base, publically, so that all the compiler generated constructors of Base (i.e. move constructor and move assignment) also be useful for them.

    2. Base class is missing a virtual destructor, and you are assigning the child pointers (i.e. in Owner class), which will be therefore deleted through Base class pointers (i.e. std::unique_ptr<Base> ptr;). This will lead to undefined behavior.

      Read here more: When to use virtual destructors?

    3. In Owner, you should be better using constructor initializer list, instead of constructing the Owner and assigning the members separately.

    4. (Optionally), you might probably want to provide a template constructor for Owner, so that one can pass both Der1 and Der2.

    The code would look like: See live demo in godbolt.org

    
    class Base
    {
    public:
        virtual void doSmth() = 0;
        virtual ~Base() = default; // add this for designed behavior!
    };
    
    class Der1 : public Base
    //           ^~~~~~~ ---> Inherit public ally
    {
    public:
        // ....
    };
    
    class Der2 : public Base
    //           ^~~~~~~ ---> Inherit public ally
    {
    public:
        // ....
    };
    
    class Owner
    {
    public:
        template<typename Derived> // --> Templated
        Owner(std::unique_ptr<Derived>&& passedptr)
            : ptr(std::move(passedptr))
        {}
    
        std::unique_ptr<Base> ptr;
    };