Copying derived entities using only base class pointers, (without exhaustive testing!) - C++
Asked Answered
D

5

43

Given a base class that is inherited by plethora of derived classes, and a program structure that requires you manage these via base class pointers to each entity. Is there a simple way to copy the entire derived object when only the base class pointer is known?

Looking around it would seem possible (if incredibly tedious) to use the dynamic_cast call to check if a base pointer can be cast as the appropriate derived class, then copy it using the derived class's copy constructor. However this is not really an optimal solution partly due to the excessive use of dynamic_cast and also it would see a total headache to maintain and extend.

Another more elegant solution I have come across is as follows:

class Base
{
public:
   Base(const Base& other);
   virtual Base* getCopy();
   ...
}

class Derived :public Base
{
   Derived(const Derived& other);
   virtual Base* getCopy();
   ...
}

Base* Base::getCopy()
{
   return new Base(*this));
}

Base* Derived::getCopy()
{
   return static_cast<Base*>(new Derived(*this));
}

Then by calling getCopy() on the Base class pointer to any derived object one still gets a base class pointer back but also the whole of the derived object has been copied. This method feels a lot more maintainable as it just requires a similar getCopy() function to be in all derived classes, and does away with the need to test against all possible derived objects.

Essentially, is this wise? or is there a better, even neater way of doing this?

Drachma answered 17/2, 2011 at 10:3 Comment(3)
When you say "copy" do you refer to assignment or creation of a new element? Creation of a new element is rather simple (as in the code in your question), while assignment is much more complicated.Clambake
What i mean by copy is, to duplicate the derived entity and return a base class pointer to this new entity. If it helps I think templatetypedef's aswer below has been able to remove any ambiguety.Drachma
+1 Good question. I'm currently boning up on C++ having been away for a few years and came across this issue when attempting to deep copy an object which had a base class pointer to a derived class object.Cherlynchernow
D
47

This approach is the preferred way of copying polymorphic objects because it offloads the responsibility of determining how to copy an object of an arbitrary type to that object, rather than trying to determine it at compile-time. More generally, if you don't know what the base class pointer points at at compile-time, you can't possibly know which of the many potential pieces of code you would need to execute in order to get a correct copy. Because of this, any working solution will need a dynamic selection of code, and the virtual function is a good way to do this.

Two comments on your actual code. First, C++ inheritance allows a derived class overriding a base class member function to have the derived function return a pointer of a type more specific than the base class version. This is called covariance. As an example, if a base class function is

virtual Base* clone() const;

Then a derived class can override it as

virtual Derived* clone() const;

And this will work perfectly fine. This allows you, for example, to have code like this:

Derived* d = // something...
Derived* copy = d->clone();

Which, without the covariant overload, wouldn't be legal.

Another detail - in the code you have, you explicitly static_cast the derived pointers to base pointers in your code. This is perfectly legal, but it's not necessary. C++ will implicitly convert derived class pointers to base class pointers without a cast. If, however, you use the covariant return type idea, this won't come up because the return type will match the type of the objects you'll be creating.

Darra answered 17/2, 2011 at 10:7 Comment(1)
Thank you for the excelent reply, and for the tips on my actually code. I was not aware of covariant types but I will definately read up on them now.Drachma
S
4

Note that you don't need the static_cast there. Derived* converts to Base* implicitly. You absolutely shouldn't use a dynamic_cast for that, as Ken Wayne suggests, since the concrete type is known at compile time, and the compiler can tell you if the cast is not allowed.

As for the approach, this pattern is standard enough to be built in to C# and Java as ICloneable and Object.clone(), respectively.

Edit:

... or is there a better, even neater way of doing this?

You could use a "self-parameterized base class", which saves you implementing the clone() function each time. You just need to implement the copy constructor:

#include <iostream>

struct CloneableBase {
    virtual CloneableBase* clone() const = 0;
};


template<class Derived>
struct Cloneable : CloneableBase {
    virtual CloneableBase* clone() const {
       return new Derived(static_cast<const Derived&>(*this));
    }
};


struct D1 : Cloneable<D1> {
    D1() {}
    D1(const D1& other) {
        std::cout << "Copy constructing D1\n";
    }
};

struct D2 : Cloneable<D2> {
    D2() {}
    D2(const D2& other) {
        std::cout << "Copy constructing D2\n";
    }
};


int main() {
    CloneableBase* a = new D1();
    CloneableBase* b = a->clone();
    CloneableBase* c = new D2();
    CloneableBase* d = c->clone();
}
Streak answered 17/2, 2011 at 10:20 Comment(4)
What if I want to inherit further classes from D1 or D2?Ferroconcrete
Then it probably doesn't help you as much. You can still override clone() manually on a derived class. And you can skip or use Cloneable<T> as you see fit.Streak
@UncleBens, you will need to virtual inherent from the Cloneable #9423260Ramsgate
would better explicit note in the answer of using CRTPRamsgate
A
0

Yeah, your idea is the way to go. It also allows the derived classes to choose whether they wish to perform a deep or shallow copy.

I have one (somewhat nit-picky) point for future reference: in terms of safety, using dynamic_cast is preferred to static_cast for polymorphic conversions. It's just one of those things that grabs my attention.

Audreaaudres answered 17/2, 2011 at 10:12 Comment(2)
I wouldn't say that dynamic_cast is always preferred. For upcasting it's not preferable to static_cast when non-virtual inheritance is being used, and when the cast is known to be safe it's an unnecessary runtime check. I'd say that dynamic_cast is best for downcasts where it's unclear whether the cast is going to succeed. In this case, it's a safe upcast, which is actually probably best done without the explicit casting at all.Darra
For upcasting you don't need static_cast or dynamic_cast. Its implicitly doneMahala
O
0
template <class T>
Base* CopyDerived(const T& other) {
  T* derivedptr = new T(other);
  Base* baseptr = dynamic_cast<Base*>(derivedptr);
  if(baseptr != NULL)
    return baseptr;
  delete derivedptr;
  // If this is reached, it is because T is not derived from Base
  // The exception is just an example, handle in whatever way works best
  throw "Invalid types in call to Copy";
}

This only requires a publicly accessible copy constructor in each derived class you wish to copy.

Oystercatcher answered 5/5, 2017 at 17:56 Comment(1)
The downside of this in the context of my original question is it requires the concrete derived type to be visible at the location you wish to copy it. In this question I was looking for a way to copy derived objects when all you can see are base class pointers/references. Some other points with your implementation: the dynamic cast is superfluous you can as explained by @Darra just do Base* ptr = new T(other);. The other possible downside here the use of a naked new allocating the copy on the heap in an unsafe way (shared_ptr/unique_ptr would at least offer some protection).Drachma
R
0

This is old but wonders why no one was thinking about CRTP, exactly for "Polymorphic copy construction": https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Copied from Wiki:

// Base class has a pure virtual function for cloning
class AbstractShape {
public:
    virtual ~AbstractShape () = default;
    virtual std::unique_ptr<AbstractShape> clone() const = 0;
};

// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape : public AbstractShape {
public:
    std::unique_ptr<AbstractShape> clone() const override {
        return std::make_unique<Derived>(static_cast<Derived const&>(*this));
    }

protected:
   // We make clear Shape class needs to be inherited
   Shape() = default;
   Shape(const Shape&) = default;
   Shape(Shape&&) = default;
};

// Every derived class inherits from CRTP class instead of abstract class

class Square : public Shape<Square>{};

class Circle : public Shape<Circle>{};

It works because now Base class always can access the derived class members without overriding virtual for each derived class, thanks to the templates instantiated.

Ramsgate answered 7/1, 2021 at 21:15 Comment(2)
Is this not the same answer as provided by @Martin-Stone just using shared and unique pointers instead of raw?Drachma
yes, the idea is the same. I overlooked @Martin-Stone's answerRamsgate

© 2022 - 2024 — McMap. All rights reserved.