c++templatesinheritanceclonecloneable

The best way to implement cloneable c++ classes?


I have seen solutions (including in this site) to the problem of having to implement a clone method in a class, so it returns a heap-allocated clone of itself even if we only have the Baseclass.

The problem come up when you have to implement in a lot of classes the same method again and again, and I remember a way of doing this when you only have to inherit from a template class (name it Cloneable) so it implements it, but the way i'm trying to do it doesn't work:

template<typename T, typename B> class Cloneable{
public:
    B* clone(){return new T(*this);}
};

class Bottom {
public:
    virtual void sayhi() = 0;
    virtual Bottom* clone() = 0;
};

class Middle: public Cloneable<Middle, Bottom>, public Bottom {
public:
    void sayhi() override {cout << "Hi from the Middle" << endl;}
};

//class Top: public Middle ...?

int main() {
    Bottom* b1 = new Middle();
    b1->sayhi();
    delete b1;
}

I remember that the person that showed this to me actually did it with just one argument in Cloneable, but anyways, i would thank any way that you can imagine of doing this.


Solution

  • You could use the Curious Recurring Template Pattern where a derived class actually derives from an instanciation of it base class depending on itself. Demo:

    #include <iostream>
    
    template<typename T>
    class Clonable {
    public:
        T* clone() {
            return new T { * static_cast<T *>(this)};
        }
    };
    
    template<class T>
    class Base : public Clonable<T> {
    public:
        int x = 2;
    
        virtual ~Base() {}
    };
    
    class Derived : public Base<Derived> {
    public:
        int y = 3;
    };
    
    int main() {
        Base<Derived> *d = new Derived;
    
        static_cast<Derived *>(d)->y = 6;
    
        Derived *c = d.clone();  // we are even cloning from a Base *!
        std::cout << c->x << ' ' << c->y << '\n';
        delete c;
        delete d;
    }
    

    It compiles with no warning and correctly outputs 2 6, proving that no slicing occurred.