Copying a Polymorphic object in C++
Asked Answered
C

3

46

I have base-class Base from which is derived Derived1, Derived2 and Derived3.

I have constructed an instance for one of the the derived classes which I store as Base* a. I now need to make a deep copy of the object which I will store as Base* b.

As far as I know, the normal way of copying a class is to use copy constructors and to overload operator=. However since I don't know whether a is of type Derived1, Derived2 or Derived3, I cannot think of a way of using either the copy constructor or operator=. The only way I can think of to cleanly make this work is to implement something like:

class Base
{
public:
  virtual Base* Clone() = 0;

};

and the implement Clone in in the derived class as in:

class Derivedn : public Base
{
public:
  Base* Clone() 
  {
    Derived1* ret = new Derived1;
    copy all the data members
  }
};

Java tends to use Clone quite a bit is there more of a C++ way of doing this?

Clipper answered 28/2, 2011 at 23:13 Comment(4)
The optimal way to do this might depend on the bigger picture why and in which situation you are trying to do this...Allergic
I have a complex tree of different objects, some polymorphic. I want to duplicate the tree using a recursive algorithm.Clipper
Hope this helps: #3831870. Follow the links in the sentence mentioned as "this, this and this"Southeastwards
Clonable pattern has a few important properties you need to be aware of. This article is worth a read: herbsutter.com/2019/10/03/…Mastoidectomy
S
46

This is still how we do stuff in C++ for polymorphic classes, but you don't need to do the explicit copy of members if you create a copy constructor (possibly implicit or private) for your objects.

class Base
{
public:
  virtual Base* Clone() = 0;
};

class Derivedn : public Base
{
public:
  //This is OK, its called covariant return type.
  Derivedn* Clone() 
  {
    return new Derivedn(*this);
  }
private:
  Derivedn(const Derivedn&) : ... {}
};
Saddletree answered 28/2, 2011 at 23:18 Comment(7)
Clone() doesn't have to be abstract. If the subclasses shared common data, there could be a copy constructor for the base class to copy the common parts, and a virtual copy constructor to handle copying the variant parts. Either way it's done, every derived class needs its own cloning method.Oxpecker
Why use the covariant return type here? Doesn't Base* Derivedn::Clone() { return new Derivedn(*this); } work equally well?Tittle
It does work just as well until you need access to the Derived for some reason - it costs nothing to use covariant return here and may be helpful later. (And I've certainly found it useful when using this kind of pattern)Saddletree
This is alot of biolerplaite code. The Clone function has to be written out for every derived class. is there no way to allocate a new instance of a class that will be specified at run-time?Sepia
@JohannesGerer The reason for adding it to each derived class is to explicitly add type information that only that class is guaranteed to have. No other place with only language guarantees know it is a Derivednexception Derivedn. Now with C++11 there is likely some easy way to do that with decltype and auto.Affective
So In have to implement the exactly same method for every subclass? That's just gross :(Vindicable
@JohannesGerer The answer for your question is here: #5731717.Sinusoidal
K
0
template <class T>
Base* Clone (T derivedobj) {
  T* derivedptr = new T(derivedobj);
  Base* baseptr = dynamic_cast<Base*>(derivedptr);
  if(baseptr != NULL) {
    return baseptr;
  }
  // this will be reached if T is not derived from Base
  delete derivedptr;
  throw std::string("Invalid type given to Clone");
}

The only thing this function requires of the derived classes is that their copy constructor is publicly accessible.

Keitloa answered 4/5, 2017 at 18:55 Comment(2)
But it can't be called polymorphically. The parameter is passed by value and will cause slicing in any polymorphic usage.Poussette
There is more wrong with this code. Clone is taking its argument by value which means you called its copy constructor already! This function, with its dynamic_cast and backing out if it was the wrong type, is equivalent (in the case where it works) to just Base* y = new<T>(derivedobj); but here the error (T is not derived from Base) is found at compile time. This does not solve the OP's problem at all, since you have to know the actual T and also downcast the original pointer back to its actual type before using this.Skimmer
C
0

I have seen some answers using a template function to clone objects. Let me show you how that will not work. Consider the following code:

This is a special case that shows up when objects are being received from a container of Base objects. The function will return a pointer to the Base even when obj is of type Derived. The template only works when it is called by an object that has not undergone any casting.

#include <iostream>
#include <memory>
#include <vector>

class Base{
public:
    virtual void foo(){}
};

class Derived : public Base{};

template<typename T>  std::shared_ptr<T> foo(const T& obj){
    std::cout << "obj is of type: " << typeid(obj).name() << std::endl;
    std::cout << "T is of type: " << typeid(T).name() << std::endl;
    std::cout << std::endl;
    return std::make_shared<T>(obj); // returns Base pointer
}

int main()
{
    std::vector<std::shared_ptr<Base>> vec {std::make_shared<Base>(), std::make_shared<Derived>()};
    for(auto c: vec)
        foo(*c);

    return 0;
}

/* OUTPUT:
obj is of type: 4Base
T is of type: 4Base

obj is of type: 7Derived
T is of type: 4Base

*/
Cleavers answered 5/11, 2021 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.