Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a unique_ptr to a value passed by base class reference?

I have Base and Derived classes that I need polymorphic behaviour from, via the virtual member function foo():

#include <iostream>
#include <memory>

class Base {
public:
    virtual int foo() const = 0;
};

class Derived : public Base {
public:
    int foo() const override { return 42; }
};

std::unique_ptr<Base> clone(Base & base) {
    //auto copy = base;  // cannot create an abstract type
    //return std::make_unique(copy);

    return std::make_unique(base);
}

int main() {
    auto d = Derived();

    auto p = clone(d);
 
    std::cout << p->foo() << '\n';
}

Does not compile: https://godbolt.org/z/voaGdf1sM

<source>: In function 'std::unique_ptr<Base> clone(Base&)':
<source>:19:28: error: no matching function for call to 'make_unique(Base&)'
   19 |     return std::make_unique(base);
      |            ~~~~~~~~~~~~~~~~^~~~~~

Outside of clone(), I want to be able to treat instances of Derived with value semantics throughout most of the program, but there is a case where I need to add instances of Derived to a collection (std::vector<std::unique_ptr<Base>>) and retain polymorphism, so in this case I need to be able to add unique_ptrs to new copies of the Derived objects. Thus I have the function clone() which takes a reference to a Derived object and is intended to make a copy that is then owned by a unique_ptr, and this is returned.

Unfortunately I cannot work out how to make a polymorphic copy of a reference to an abstract type like Base.

I cannot pass the parameter by value, and then move the implicit copy into the unique_ptr, as it's not possible to have an instance of Base, and I need the polymorphism.

I looked into using a forwarding reference, something like:

std::unique_ptr<Base> clone(Base && base) {
    return std::make_unique(base);
}

// ...
auto p = clone(std::move(d));

But I don't want to expose the ownership at the caller and require them to call std::move - it should be free to pass in a reference to an existing object and expect clone to copy it, not take ownership of it. I actually want it to be copied, and for the source object to remain intact and able to be used again (e.g. to make more copies).

Note: if I could get this working, it would probably be better for clone to take a const Base &, as I'm passing by reference for performance reasons after all.

Is what I'm trying to do here - make a copy, that is managed as a unique_ptr, without imposing move semantics on the caller - even possible?

like image 684
davidA Avatar asked Oct 28 '25 04:10

davidA


2 Answers

I think you're looking for something like this:

#include <iostream>
#include <memory>

class Base {
public:
    virtual ~Base () { }
    virtual int foo() const = 0;
    virtual std::unique_ptr <Base> clone (void) const = 0;
};

class Derived : public Base {
public:
    int foo() const override { return 42; }

    std::unique_ptr <Base> clone (void) const override
    {
        auto result = std::make_unique <Derived> ();
        *result = *this;
        return result;
    }
};

int main() {
    auto d = Derived ();
    auto p = d.clone ();
    std::cout << p->foo() << '\n';
}

I'm not sure there's much else to say really, although you can also do:

Base &base = d;
...
auto p = b.clone ();

which I think gets closer to what you're asking for.

Live demo


Edit: added a virtual destructor to Base as per @eerorika's comment, how sloppy of me to omit it!

like image 63
Paul Sanders Avatar answered Oct 29 '25 17:10

Paul Sanders


The unfortunate potential repetition in Paul's solution can be alleviated using the Curiously recurring template pattern:

template <class T>
class TBase : public Base {  // Base from Paul's answer
public:
    std::unique_ptr<Base> clone() const override {
        const T& ref = static_cast<const T&>(*this);
        return std::make_unique<T>(ref);
    }
};

class Derived1 : public TBase<Derived1> {
public:
    int foo() const override { return 42; }
};

class Derived2 : public TBase<Derived2> {
public:
    int foo() const override { return 1337; }
};
like image 22
eerorika Avatar answered Oct 29 '25 19:10

eerorika