所有,这似乎是一个简单的问题,但它让我很适应。假设我有一个名为 Animal 的 C++ 基类和派生类 Cat、Dog、Horse、Hippo、Snake。我有另一个名为 Zoo 的类,其中包含 Animal 对象的列表(在本例中为 Qt 列表,其中基类 Animal 是 QObject)。现在我想做的是创建一个 Zoo 对象,并能够将它复制到另一个 Zoo 对象,并让目标 Zoo 拥有它自己的 Animals 副本。问题出在 Zoo 复制构造函数中,因为它认为它有一个 Animals 列表,它调用 Animal 的复制构造函数而不是 Dog、Cat 等,因为编译器应该这样做。为了解决这个问题,我将 Animal 列表更改为 Animal 指针列表,然后创建了自己的 Animal 复制构造函数和赋值运算符。这些中的每一个都调用 Animal::Clone( Animal & rhs ) 方法,该方法依次调用 Animals Clone() 方法的每个派生类,该方法返回从堆分配的新指针及其数据的副本。这一切都很好,但我一直认为我错过了一个更优雅的解决方案。所以我的问题是,当你有一个包含某种类型对象的容器时,我们如何复制包含该容器的类?我希望这是有道理的。
1 回答
您所做的几乎是在 C++ 中的惯用方式。虚拟clone()
方法实际上被称为虚拟复制构造函数习语。通常,您会将其声明为以下签名的虚拟抽象方法。
Animal * clone() const = 0;
请注意,它不带任何rhs
参数,它只是返回自身的克隆。给定一个动物的实例Animal * myPet
,你可以通过说得到一个重复的实例
Animal * mySecondPet = myPet->clone();
如果要实例化派生类,则必须在派生类中实现它。
[Ed:]正如您所发现的,由于C++ 设计中固有的切片问题,容器类不可能直接而不通过指针来保存项目。
在 C++ 中,您的解决方案是一个习惯用法:它非常优雅,每个看到它的人,知道他们的 C++ 的人,都应该立即明白您的意思。习语和某些设计模式是语言的一部分。如果不知道成语,你就不能称自己精通一门语言,无论是人类语言还是编程语言。
在我的许多项目中,我使用一个名为 的接口类(所有抽象虚拟方法),它只Cloneable
包含这个克隆方法。然后很容易强制一些基类是可克隆的:只需从 Cloneable 继承。例如,我希望QEvent
是Cloneable
这样。事实上,不可能复制QEvents
将它们发布到多个事件队列。
Daniel 的建议“您是否尝试过将 Animal 的复制构造函数设为虚拟?” 不能从字面上理解。在 C++ 中实际上没有虚拟复制构造函数或任何构造函数之类的东西——原因很简单:在构造对象之前,它的虚拟方法表还没有最终确定。具体来说,Animal 构造函数中的代码将在调用任何派生类的构造函数以将虚拟方法表指针换出到派生类中的表指针之前执行。因此,如果您在构造函数中进行了任何虚拟方法调用,它们将不会转到从您的类派生的任何类。就是这样。