The virtual clone pattern is often used to solve problems such as these. Classic solutions tend to use co-variant return types for the clone()
method. Other solution inject a factory type class (using CRTP) between the base and the derived classes. There are even solutions that just implement this functionality with macros. See the C++ FAQ, C++ idioms and a blog on this. Any one of these solutions is viable and the most appropriate would depend on the context in which they are used and intended to be used.
A classic approach, using covariant return types and coupled with more modern RAII techniques (shared_ptr
et. al.) offers a very flexible and safe combination. One of the advantages of the covariant return type is that you are able to obtain a clone at the same level in the hierarchy as the argument (i.e. the return is not always to the base class).
The solution does require access to shared_ptr
and/or unique_ptr
. If not available with your compiler, boost provides alternatives for these. The clone_shared
and clone_unique
are modelled on the corresponding make_shared
and make_unique
utilities form the standard library. They contain explicit type checks on the class hierarchy of the arguments and target types.
#include <type_traits>
#include <utility>
#include <memory>
class CBaseClass {
public:
virtual CBaseClass * clone() const {
return new CBaseClass(*this);
}
};
class CDerivedClass : public CBaseClass {
public:
virtual CDerivedClass * clone() const {
return new CDerivedClass(*this);
}
};
class CMoreDerivedClass : public CDerivedClass {
public:
virtual CMoreDerivedClass * clone() const {
return new CMoreDerivedClass(*this);
}
};
class CAnotherDerivedClass : public CBaseClass {
public:
virtual CAnotherDerivedClass * clone() const {
return new CAnotherDerivedClass(*this);
}
};
// Clone factories
template <typename Class, typename T>
std::unique_ptr<Class> clone_unique(T&& source)
{
static_assert(std::is_base_of<Class, typename std::decay<decltype(*source)>::type>::value,
"can only clone for pointers to the target type (or base thereof)");
return std::unique_ptr<Class>(source->clone());
}
template <typename Class, typename T>
std::shared_ptr<Class> clone_shared(T&& source)
{
static_assert(std::is_base_of<Class, typename std::decay<decltype(*source)>::type>::value,
"can only clone for pointers to the target type (or base thereof)");
return std::shared_ptr<Class>(source->clone());
}
int main()
{
std::unique_ptr<CDerivedClass> mdc(new CMoreDerivedClass()); // = std::make_unique<CMoreDerivedClass>();
std::shared_ptr<CDerivedClass> cloned1 = clone_shared<CDerivedClass>(mdc);
std::unique_ptr<CBaseClass> cloned2 = clone_unique<CBaseClass>(mdc);
const std::unique_ptr<CBaseClass> cloned3 = clone_unique<CBaseClass>(mdc);
// these all generate compiler errors
//std::unique_ptr<CAnotherDerivedClass> cloned4 = clone_unique<CAnotherDerivedClass>(mdc);
//std::unique_ptr<CDerivedClass> cloned5 = clone_unique<CBaseClass>(mdc);
//auto cloned6 = clone_unique<CMoreDerivedClass>(mdc);
}
I've added a CMoreDerivedClass
and CAnotherDerivedClass
to expand the hierarchy a little to better show types checks etc.
Sample code
clone
?Clone
should be a virtual member method implemented by each class of your hierachy, includingCDerivedClass