如何最佳实现可克隆的C++类?

3

我看到过一些解决方案(包括在这个网站上)来解决在一个类中实现clone方法的问题,以便它返回一个堆分配的自身克隆,即使我们只有Base类。

问题在于当你不得不在许多类中再次实现相同的方法时,会出现问题。我记得有一种方式可以做到这一点,那就是继承一个模板类(命名为Cloneable)使其实现它,但我尝试的方法行不通:

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;
}

我记得向我展示这个的人实际上只使用了 Cloneable 中的一个参数,但无论如何,我都会感谢您能想象出的任何方式来完成这个。


1
你在模板参数中使用了 Middle,但它还没有被定义。任何继承自抽象类的东西都应该重写抽象类中的函数,否则子类也将是抽象的。 - IcanCode
@IcanCode 我明白了,但一定有办法。 - Jonnas Kaf
没有办法做你想要的事情。首先,从Cloneable的角度来看,*thisCloneable&,因此new T(*this)期望一个构造函数,该构造函数需要一个Cloneable&,而不是Middle&或其他任何类型。其次,在Cloneable中具有clone并不意味着实现了Bottom中的虚拟clone。在像Java这样的语言中,您可以这样做,但在C ++中不可能。 - Holt
你必须在许多类中重复实现同一个方法:为什么不将它们放到基类中呢? - user1196549
@YvesDaoust 因为基类不知道要 new 什么类型。 - Caleth
将函数移动到派生类中如何解决这个问题? - user1196549
2个回答

6
您可以使用“奇异递归模板模式”(Curiously Recurring Template Pattern,CRTP),其中一个派生类实际上是从其自身的基类实例化中派生出来的。演示如下:

您可以使用 奇异递归模板模式 ,其中派生类实际上是从基类的实例化中派生出来的,该基类取决于它本身。 演示:

#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;
}

它编译时没有警告,并正确输出2 6,证明没有发生切片。


你比我更快地回复了关于crtp的问题,尽管我正准备在评论中附上这个链接:https://herbsutter.com/2019/10/03/gotw-ish-solution-the-clonable-pattern/ - stefaanv
2
在你的主函数中,Derived d; 应该改为 Base* d = new Derived;,而且 Base *c = d->clone(); 仍然应该返回一个 Derived。 - stefaanv
这正是我正在寻找的! - Jonnas Kaf
1
问题在于现在Base是一个模板,而不是多态的。 - Caleth
@SergeBallesta:我自己尝试了一下,不再信服。顺便说一句, Base* d = ... 应该是 Base<Derived>* d = ...,这使它变得不太有用。 - stefaanv
显示剩余2条评论

2

您的Cloneable缺少一个强制转换。 this是一个Cloneable *,它需要被转换为T *才能传递给复制构造函数。

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

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

class Derived : public Cloneable<Derived, Base>
{
};

点击查看实例

这不能阻止某人进一步使用Derived进行子类化,并忘记为该孙子类重新实现clone

class Oops : public Derived {};

int main() {
    Oops o;
    Base * pb = o.clone();
    assert(typeid(*pb) != typeid(o));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接