3

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?

davidA
  • 12,528
  • 9
  • 64
  • 96
  • 1
    `create` is not a good name for a function that makes a copy. `copy` or `clone` would be better – 463035818_is_not_an_ai Mar 25 '23 at 22:43
  • Yes, that's a fair point, and I agree 100%. I originally had it creating a `std::vector>` and returning that, but I stripped that out to make it simpler, and inadvertently left the function called `create`. EDIT: I've renamed "create" to "clone" in my question. – davidA Mar 25 '23 at 23:13
  • 1
    Found some related questions, but they assume that `clone()` is a (virtual) member function: [How can a derived C++ class clone itself via a base pointer?](https://stackoverflow.com/q/3136646/) -- [Is it possible to clone a polymorphic object without manually adding overridden clone method into each derived class in C++?](https://stackoverflow.com/q/55076438/) -- [Clone derived class from base class pointer](https://stackoverflow.com/q/65916601/) – JaMiT Mar 25 '23 at 23:21
  • @JaMiT those links are very useful - thank you. – davidA Mar 25 '23 at 23:28

3 Answers3

3

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!

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Thank you. So it needs explicit virtual machinery. I think that this requires every derived class to implement its own version of `clone`, which is manageable, but unfortunate. I do appreciate, however, that this will work in more situations than the templated technique I found elsewhere and posted as another answer. – davidA Mar 25 '23 at 23:10
  • 1
    Note that behaviour of this example program is undefined (as would be the OP's attempt if it compiled in the first place). To fix it, make the destructor of `Base` virtual. – eerorika Mar 26 '23 at 00:03
  • _I think that this requires every derived class to implement its own version of `clone`_ Indeed, although `Base` could always implement a default. – Paul Sanders Mar 26 '23 at 07:58
3

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; }
};
eerorika
  • 232,697
  • 12
  • 197
  • 326
0

One partial solution I came across elsewhere on SO is to use a templated clone() function to handle the concrete type:

#include <iostream>
#include <memory>

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

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

template <typename T>
std::unique_ptr<Base> clone(T const & t) {
    return std::make_unique<T>(t);
}

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

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

https://godbolt.org/z/GbTz4nb3E

This lets make_unique invoke the copy-constructor on the supplied parameter as if it were a Derived, as the concrete type is known by the caller. I'm not sure this would still work if the caller only had a reference or pointer to Base though.

davidA
  • 12,528
  • 9
  • 64
  • 96
  • 2
    _I'm not sure this would still work if the caller only had a reference or pointer to Base though._ Don't see how it could, no. – Paul Sanders Mar 26 '23 at 08:01