11

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?

3

4 Answers 4

6

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

3
  • You will want to call make_shared to avoid memory allocation overhead when cloning shared pointers.
    – jxh
    Commented Jul 25, 2014 at 14:39
  • @jxh, good point. make_shared though creates the object itself, which is what the clone method is also doing; hence a conflict because the clone_shared method doesn't actually know what the most derived type of its argument is (only the relationship between the argument of the target). I think it could still be possible though, but I felt it beyond the scope of the question. I think the clone_unique would almost always make more sense, but the code isn't limited to that.
    – Niall
    Commented Jul 25, 2014 at 14:50
  • Knowledge of the derived class is the advantage CRTP provides. Neither approach offers a perfect solution, I concur.
    – jxh
    Commented Jul 25, 2014 at 15:07
2

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);
    }
};
16
  • 2
    @Puppy Covariant return didn't work with unique_ptr so I gave up. Please advise if you have a better solution.
    – Neil Kirk
    Commented Jul 24, 2014 at 17:21
  • 1
    @NeilKirk: The only time you need to clone is when you don't know the most derived type anyway, so there's no downside to simply returning a std::unique_ptr<Base> that I can think of. Commented Jul 24, 2014 at 17:31
  • 1
    What's the advantage of using covariant return here? Commented Jul 24, 2014 at 18:05
  • 2
    @Puppy Why on earth would you use anything but a raw pointer when cloning? And why would you overload the works with CRTP? The first is probably a design error, and CRTP is just unnecessary additional complication. Commented Jul 24, 2014 at 18:06
  • 1
    I agree with Mooing Duck, they're should be no issue with unique_ptr<Base>, but if the covariant return is important, you can create a make style, akin to make_unique, factory that immediately binds it to the unique_ptr that is desired.
    – Niall
    Commented Jul 24, 2014 at 18:19
1

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;
}
0

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.

4
  • 1
    What happens if you have a Base pointer and don't know the exact type? You should return DERIVED *
    – Neil Kirk
    Commented Jul 24, 2014 at 16:55
  • dynamic_cast is unnecessary and you should use a smart pointer here.
    – Puppy
    Commented Jul 24, 2014 at 17:12
  • It seems odd having to pass the object-to-be-cloned into the cloning function. I'm accustomed to invoking cloning like this: cloned_object = some_object.clone().
    – David K
    Commented Jul 24, 2014 at 17:34
  • 1
    It seems very odd that something you'd clone will be managed by a std::unique_ptr; it's presumably complete after the cloning, and doesn't need any further management. Commented Jul 24, 2014 at 18:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.