hyperdelia
hyperdelia

Reputation: 1145

Copy contents of unique_ptr to unknown derived class

I have an abstract base class, with several concrete derived classes; none of these classes manage any resources.

#include <memory>
#include <vector>

// this is a pure abstract class that contains no resources
class Base {
public:
    Base() {};
    virtual int doSomething() = 0;
};

class Derived : public Base {
public:
    Derived()  {};

    // this mutates the derived class
    int doSomething() override { return 0; };
};

class Derived2 : public Base {
public:
    Derived2() {};

    // this mutates the derived class
    int doSomething() override { return 0; };
};

and I have a function that returns a random derived instance (Derived1, Derived2, Derived3, depending upon a random number throw).

std::unique_ptr<Base> randomDerivedInstance() {
    // pick a random number here and return Derived1 or Derived2 etc.
    // for the purpose of this problem, I'm just returning a fixed derived class
    return std::make_unique<Derived>();
}

and I have a struct that I want to store this derived instance in

struct DataStruct {

    // this can contain Derived1 or Derived2
    std::unique_ptr<Base> base;

    // other things in struct omitted for clarity

    // obviously this won't work
    DataStruct(std::unique_ptr<Base> base) : base(base) {};
};

I return a unique pointer from my random function, and want to save a copy into the struct, and then call doSomething on it that performs several mutating operations on the class internals, and I don't want them to affect the copy stored in the list.

If I knew the type of the derived instance, I would use a copy constructor to create a new instance and add it to the vector, but in this situation I don't know the specific type of the instance I'm trying to add, so I don't know which specific constructor to use.

int main() {
    // I want to create a vector of random instances
    std::vector<DataStruct> list;

    // I create a random instance
    auto myDerived = randomDerivedInstance();

    // and I want to push a copy of myDerived before I do something with it
    // obviously this doesn't work because its a unique_ptr
    // what can I do here?
    list.push_back(DataStruct(myDerived));

    // do something that mutates myDerived
    myDerived->doSomething();

    // I don't want my mutations to myDerived to affect the list copy


}

The code above doesn't compile for obvious reasons since I'm trying to assign a unique_ptr in the DataStruct constructor.

What changes do I need to make to this architecture and code in order to get this to work as intended? i.e. add a value-copy of random derived instance to a struct, so that I can mutate the original instance (or vice-versa, add original, and mutate copy).

Thanks for all help in advance!

Upvotes: 0

Views: 836

Answers (1)

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145279

In class Base add a virtual member function clone:

virtual auto clone() const
    -> std::unique_ptr<Base>
    = 0;

In each derived class Derived override that to provide a derived class specific clone:

auto clone() const
    -> std::unique_ptr<Base>
    override
{ return std::unique_ptr<Base>( new Derived{ *this } ); }

It's possible to do this in a more advanced way where if you know the most derived class at compile you can get a clone statically of that type, but it doesn't appear that you need that.

Disclaimer: off-the-cuff code, not reviewed by compiler.


At one time, long ago, a clone function was called a virtual constructor, and that term is used in the FAQ item about this. I think it was introduced by Coplien. The current FAQ text doesn't say.


Also worth noting: in C++11 and later the generation of clone function implementations can be partially automated by Derived inheriting from an implementation that in turn inherits from Base, with forwarding of constructor arguments.

C++03 didn't support forwarding so then one had to use schemes such as code-generating macro (evil but in practice the only real solution back then), implementation inheritance via dominance in a virtual inheritance hierarchy (extremely complex and ugly), or to do the same as we now can do in C++11 and later, but with a Do-It-Yourself argument forwarding scheme (somewhat arbitrary limited).

For an overview of these old C++03 techniques see my 2010 blog article “3 ways to mix in a generic cloning implementation”.

Upvotes: 2

Related Questions