2

I have a need to clone a derived class given only a reference or pointer to the base class. The following code does the job, but doesn't seem elegant, because I'm putting boilerplate code into many derived classes C, D, E that are siblings of B (not shown) that just calls the default copy constructor of each. Isn't that what the default copy constructor is for, if only it could be virtual?

Is there a better way?

Making a virtual assignment operator would be wrong, as I don't want C to assign to B, B to D, etc, just clone B, C, D or E.

#include <iostream>
using namespace std;

class A {
public:
    virtual ~A() {}
    virtual A* clone()=0;
};

class B : public A {
    int i;
    public:
    virtual A* clone() { 
        cout << "cloned B" << endl;
        return new B(*this);
    }
    virtual ~B() { cout << "destroyed b" << endl; }
};

int main() { 
    A* a = new B(); 
    A* aa = a->clone();
    delete a; 
    delete aa; 
    return 0;
}
Sideshow Bob
  • 4,408
  • 5
  • 35
  • 77

3 Answers3

5

You could always stick all the cloning logic into its own class in the middle of the hierarchy:

template <class Derived, class Base>
class CloneCRTP : public Base {
public:
    Derived* clone() const override {
        return new Derived(static_cast<Derived const&>(*this));
    }
};

And then:

class B : public CloneCRTP<B, A>
{
    int i;
public:
    virtual ~B() { cout << "destroyed b" << endl; }        
};

No more boilerplate.

Barry
  • 267,863
  • 28
  • 545
  • 906
  • 2
    No boilerplate, but recursive templates and downcasting... I should be careful what I wish for! – Sideshow Bob Mar 23 '16 at 22:45
  • I think this approach [doesn't work well as an actual covariant return type](http://coliru.stacked-crooked.com/a/376c5c745470fc27) and there is a need [for some additional trick](https://stackoverflow.com/questions/36189838/c-elegantly-clone-derived-class-by-calling-base-class/64354416#64354416). – Amir Kirsh Oct 14 '20 at 13:36
2

You can rely on the CRTP idiom.
It follows a minimal, working example:

struct B {
    ~virtual ~B() { }
    virtual B* clone() = 0;
};

template<class C>
struct D: public B {
    B* clone() {
        return new C{*static_cast<C*>(this)};
    }
};

struct S: public D<S> { };

int main() {
    B *b1 = new S;
    B *b2 = b1->clone();
    delete b1;
    delete b2;
}
skypjack
  • 47,379
  • 17
  • 84
  • 173
1

To achieve clone that works as with covariant return type, there is a need for some more complicated CRTP:

class Shape {
    // virtual
    virtual Shape* do_clone() const = 0;
public:
    virtual ~Shape() {}
    // non-virtual
    Shape* clone() const {
        return do_clone();
    }
    // ...
};

template <class Derived, class Base>
class CloneCRTP : public Base {
    // virtual
    Shape* do_clone() const override {
        return new Derived(static_cast<Derived const&>(*this));
    }
public:
    // non-virtual
    Derived* clone() const {
        return static_cast<Derived*>(do_clone());
    }    
};

Usage:

class Rectangle: public CloneCRTP<Rectangle, Shape> { /*...*/ };    
class Triangle: public CloneCRTP<Triangle, Shape> { /*...*/ };

int main() {
    Rectangle* r1 = new Rectangle{};

    // getting the proper type from clone:
    Rectangle* rcopy = r1->clone();

    delete rcopy;
    delete r1;

    Triangle t1{};

    // getting the proper type from clone:
    Triangle* tcopy = t1.clone();

    delete tcopy;
}

Code: http://coliru.stacked-crooked.com/a/d8781deee5f7f6ea

Amir Kirsh
  • 10,335
  • 31
  • 57