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?

alancc
  • 487
  • 2
  • 24
  • 68
  • 4
    How about a virtual member function `clone`? – Baum mit Augen Jul 24 '14 at 16:49
  • 2
    `Clone` should be a virtual member method implemented by each class of your hierachy, including `CDerivedClass` – quantdev Jul 24 '14 at 16:49
  • possible duplicate of [How to clone object in C++ ? Or Is there another solution?](http://stackoverflow.com/questions/12902751/how-to-clone-object-in-c-or-is-there-another-solution) – quantdev Jul 24 '14 at 16:52

3 Answers3

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

Niall
  • 30,036
  • 10
  • 99
  • 142
  • You will want to call `make_shared` to avoid memory allocation overhead when cloning shared pointers. – jxh Jul 25 '14 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 Jul 25 '14 at 14:50
  • Knowledge of the derived class is the advantage CRTP provides. Neither approach offers a perfect solution, I concur. – jxh Jul 25 '14 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);
    }
};
Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
  • Nice use of covariant return type, but that probably needs some additional explanation. – Ben Voigt Jul 24 '14 at 17:07
  • 2
    @Puppy Covariant return didn't work with unique_ptr so I gave up. Please advise if you have a better solution. – Neil Kirk Jul 24 '14 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` that I can think of. – Mooing Duck Jul 24 '14 at 17:31
  • 1
    What's the advantage of using covariant return here? – James Kanze Jul 24 '14 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. – James Kanze Jul 24 '14 at 18:06
  • @MooingDuck The disadvantage is that the `std::unique_ptr` will delete the object if it accidentally goes out of scope, and you generally don't want that when cloning; it will lead to dangling pointers and a double delete down the road. – James Kanze Jul 24 '14 at 18:07
  • 1
    I agree with Mooing Duck, they're should be no issue with `unique_ptr`, 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 Jul 24 '14 at 18:19
  • @JamesKanze: If the variable accidentally goes out of scope, the _advantage_ is that the `std::unique_ptr` will delete the object. Anything else would probably be a memory leak. – Mooing Duck Jul 24 '14 at 18:25
  • @JamesKanze: The benefit of CRTP is that it offloads the burden of implementing boiler-plate clone code from every derived instance of the base, at the cost of derived classes have to remember to inherit from the CRTP helper. – jxh Jul 24 '14 at 19:00
  • 1
    @JamesKanze Because I may have car -> vehicle, and a function dealing with cars, and I want to use their clones as cars without casting. I don't know what brand of car it is. – Neil Kirk Jul 24 '14 at 20:26
  • @MooingDuck The problem with `std::unique_ptr` is that it will delete the object, probably resulting in a dangling pointer. The usual reason for creating an object on the heap is that it has an arbitrary lifetime that _doesn't_ respect scope. `std::unique_ptr` is useful in factory methods and such, where the object hasn't become fully live. But after a `clone`, I would expect that the object is fully live, and `std::unique_ptr` is more likely a source of problems than anything else. – James Kanze Jul 25 '14 at 08:09
  • @jxh So what is the large amount of boiler plate code that it replaces here. Using CRTP for `clone` will in fact increase the amount of boiler plate, requiring an additional derivation, etc. In this case, it's just extra complexity, for no benefit. – James Kanze Jul 25 '14 at 08:11
  • 1
    @NeilKirk Circumstances vary, but most of the time, the context of `clone` means that you're dealing exclusively with base class pointers anyway. – James Kanze Jul 25 '14 at 08:12
  • @JamesKanze: The "largeness" depends on how many derived types there are, and there is no "additional" derivation, just a different one. – jxh Jul 25 '14 at 08:15
  • @jxh The additional source code complexity is O(n) for n derived classes. Both with the classical implementation and CRTP. The constant factor is slightly smaller with the classical implementation. As for what is behind the source code, CRTP is significantly more complicated. – James Kanze Jul 25 '14 at 09:02
  • 1
    @JamesKanze: I am not sure where the smaller constant factor is coming from. The CRTP solution I present only requires to change the parent from the interface to the CRTP wrapper, instead of inheriting from base and implementing the cloning code. If using the CRTP wrapper, any fixes and additional cloning functionality would be inherited by all users of the wrapper, and not need individual implementation in each derived class. – jxh Jul 25 '14 at 14:42
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.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • 1
    What happens if you have a Base pointer and don't know the exact type? You should return `DERIVED *` – Neil Kirk Jul 24 '14 at 16:55
  • `dynamic_cast` is unnecessary and you should use a smart pointer here. – Puppy Jul 24 '14 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 Jul 24 '14 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. – James Kanze Jul 24 '14 at 18:04