3

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,566
  • 5
  • 42
  • 79

4 Answers4

6

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
  • 286,269
  • 29
  • 621
  • 977
  • 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
  • 49,335
  • 19
  • 95
  • 187
2

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
    Base* 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/339458f8b45299c3

YurkoFlisk
  • 873
  • 9
  • 21
Amir Kirsh
  • 12,564
  • 41
  • 74
0

I do it in a generalized way.

Definition:

template <class Class>
class CloneInterface {
public:
    Class* copy() const { return new Class{static_cast<Class const&>(*this)}; }
    virtual Class* clone() const { return this->copy(); }
};

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

Usage:

class Base : public CloneInterface<Base> {
private:
    int a = 0;
    
public:
    virtual ~Base() {}

    void print() { std::cout << "Base: a = " << a << '\n'; }
};

class A : public CloneCRTP<A, Base> {
private:
    int b = 1;
    
public:
    void print() { std::cout << "A: b = " << b << '\n'; }
};

Example:

int main() {
    auto b = new Base();
    auto b_clone = b->clone();
    auto b_copy = b->copy();
    
    b->print();         // Base print()
    b_clone->print();   // Base print()
    b_copy->print();    // Base print()

    std::cout << "=========================\n";
    
    auto a = new A();
    auto a_clone = a->clone();  // returns Base*
    auto a_copy = a->copy();    // returns A*
    
    a->print();         // A print()
    a_clone->print();   // Base print(), because clone returns pointer to the base class
    a_copy->print();    // A print()
    
    std::cout << "=========================\n";
    
    auto c = static_cast<Base*>(a);
    auto c_clone = c->clone();  // returns Base*, but A* inside
    auto c_copy = c->copy();    // returns Base*, that is really Base
    
    c->print();         // Base print()
    c_clone->print();   // Base print()
    c_copy->print();    // Base print()
    
    std::cout << "=========================\n";
    
    auto a_clone_restored = static_cast<A*>(c_clone);
    auto a_copy_restored = static_cast<A*>(c_copy); // Invalid
    
    a_clone_restored->print(); // A print()
    a_copy_restored->print();  // A print()
        
    delete b;
    delete b_clone;
    delete b_copy;
    
    delete a;
    delete a_clone;
    delete a_copy;
    
    return 0;
}

This approach allows to reuse crtp definition in all hierarchies that require clone functionality.

Code: https://coliru.stacked-crooked.com/a/269a2427fd2f919b