The clone paradigm is used to make a copy of a derived class without casting down to the base class type. Unfortunately, clone
must be implemented in each subclass (or with a mixin with CRTP).
Is there any chance that C++11's decltype
makes this unnecessary?
I don't think the code below actually copies original
, but simply points a reference to it. When I tried to use new decltype(*original)
, I get an error:
error: new cannot be applied to a reference type
.
Is clone
still the way to go in C++11? Or is there some new way to use RTTI to copy a derived class object from a base class pointer?
#include <iostream>
struct Base
{
virtual void print()
{
std::cout << "Base" << std::endl;
}
};
struct Derived : public Base
{
int val;
Derived() {val=0;}
Derived(int val_param): val(val_param) {}
virtual void print()
{
std::cout << "Derived " << val << std::endl;
}
};
int main() {
Base * original = new Derived(1);
original->print();
// copies by casting down to Base: you need to know the type of *original
Base * unworking_copy = new Base(*original);
unworking_copy->print();
decltype(*original) on_stack = *original;
on_stack.print();
return 0;
}
decltype
is a static construct. Like all C++ typing constructs, it cannot deduce the runtime type of an object. decltype(*original)
is just Base&
.
decltype
(as its name suggests) gives the declared type (static type) of the expression that it is applied to.
decltype(*original)
is Base&
, so your code will print
Derived 1
Base
Derived 1
but no copy will be made in the third case.
Clone (or some variant of the pattern) is still the way to go in C++11.
decltype
cannot and does not recover dynamic object type. It is a purely static construct.
There's no magic way to copy an object. You must call a constructor of its exact final dynamic type somewhere.
Is clone still the way to go in C++11? Or is there some new way to use RTTI to copy a derived class object from a base class pointer?
In case anybody is interested in a non-invasive cloning, C++11's lambdas seem to provide some new cloning facilities: Thinking about the issue of cloning, I came to admit that the one who manufactured the original instance of an object should also be the one who can help build a replica. Consider the case where all of your objects are manufactured by some Factory
. In this case, additionally to the usual create
interface, your factory could be equipped with a clone
interface, e.g. like so:
#include <iostream>
#include <functional>
#include <memory>
#include <unordered_map>
template <typename BASE>
struct
Factory {
private: using
TCloneFn = std::function<std::shared_ptr<BASE>(BASE const * const)>;
private:
static std::unordered_map<BASE const*,TCloneFn> cloneFnMap;
public: template <typename DERIVED_TYPE, typename...TS>
static std::shared_ptr<BASE>
create(TS...args) {
BASE* obj = new DERIVED_TYPE(args...);
const std::shared_ptr<BASE> pNewObj =
std::shared_ptr<BASE>(
obj,
[&](BASE* p){
cloneFnMap.erase(p);
delete p;
}
);
cloneFnMap[obj] = [&](BASE const * const orig){
std::shared_ptr<BASE> pClone = create<DERIVED_TYPE>(std::ref(static_cast<DERIVED_TYPE const &>(*orig)));
return pClone;
};
return pNewObj;
}
public: static std::shared_ptr<BASE>
clone(std::shared_ptr<BASE const> original) {
return cloneFnMap[original.get()](original.get());
}
};
template <typename BASE> std::unordered_map<BASE const*,typename Factory<BASE>::TCloneFn> Factory<BASE>::cloneFnMap;
class Base {
public: virtual ~Base() throw() {}
public: virtual void whoAmI() const {
std::cout << "I am Base instance " << this << "\n";
}
};
class Derived : public Base {
std::string name;
public: Derived(std::string name) : name(name) {}
public: Derived(const Derived&other) : name("copy of "+other.name) {
}
private: virtual void whoAmI() const {
std::cout << "I am Derived instance " << this << " " << name << "\n";
}
};
int main() {
std::shared_ptr<Base> a = Factory<Base>::create<Derived>("Original");
a->whoAmI();
std::shared_ptr<Base> copy_of_a = Factory<Base>::clone(a);
copy_of_a->whoAmI();
std::shared_ptr<Base> copy_of_a_copy = Factory<Base>::clone(copy_of_a);
copy_of_a_copy->whoAmI();
return 0;
}
The trick is to remember how the original was constructed in the Factory::create
method (by associating a pointer to the object with a lambda that will invoke the copy-constructor). Never throw away old blueprints, you may need them later-on;-)
Frankly, I still prefer the old clone
solution with CRTP etc, but this may trigger some new ideas or come otherwise in handy.
The code above is also at http://ideone.com/kIPFt2
EDIT: As wjl commented, the first version of the code would cause cloneFnMap
to steadily grow. After thinking it over, I decided to fix the code by introducing a custom deleter to the shared_ptr
s being returned such that the cloneFnMap
will also get cleaned up. Still, this should just be considered an experimental proof-of-concept code.