Reputation: 799
I define two classes in C++. Ones is the base class, and one is a derived class
class CBaseClass
{
…
}
class CDerivedClass : public CBaseClass
{
…
}
And want to implement a clone function as follows:
CBaseClass *Clone(const CBaseClass *pObject)
{
}
When an object of CDerivedClass is passed to Clone, then the function will also create a CDerivedClass object and return. When an object of CBaseClass is passed to Clone, then the function will also create a CBaseClass object and return.
How to implement such a feature?
Upvotes: 11
Views: 4009
Reputation: 62636
With C++23's explicit object parameters, you can do this entirely within the base class.
class CBaseClass
{
template <typename Self>
std::unique_ptr<Self> Clone(this const Self& self) const {
return std::make_unique<Self>(self);
}
…
};
With this, you don't really need the free function clone, but it's simple
std::unique_ptr<CBaseClass> Clone(const CBaseClass* base) {
return Base ? Base->Clone() : nullptr;
}
Upvotes: 1
Reputation: 30605
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.
Upvotes: 6
Reputation: 70392
You can accomplish this with a virtual Clone method, and a helper template CRTP class to implement this interface:
class CBaseClass {
//...
virtual CBaseClass * Clone () = 0;
std::unique_ptr<CBaseClass> UniqueClone () {
return std::unique_ptr<CBaseClass>(Clone());
}
virtual std::shared_ptr<CBaseClass> SharedClone () = 0;
};
template <typename DERIVED>
class CBaseClassCRTP : public CBaseClass
{
CBaseClass * Clone () {
return new DERIVED(*static_cast<DERIVED *>(this));
}
std::shared_ptr<CBaseClass> SharedClone () {
return std::make_shared<CbaseClass>(*static_cast<DERIVED *>(this));
}
};
class CDerivedClass : public CBaseClassCRTP<CDerivedClass>
{
//...
};
Now, each derived class gete a Clone
method courtesy of the helper class.
Upvotes: 0
Reputation: 21763
Here is a simple solution. Remember to provide a Clone for each class in the inheritance.
class Base
{
public:
virtual ~Base() {}
virtual Base *Clone() const
{
// code to copy stuff here
return new Base(*this);
}
};
class Derived : public Base
{
public:
virtual Derived *Clone() const
{
// code to copy stuff here
return new Derived(*this);
}
};
Upvotes: 2