Reputation: 1145
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
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