14

在 C++ 编程时,我们曾经在需要时创建复制构造函数(或者我们被教导过)。几年前切换到 Java 时,我注意到现在正在使用 Cloneable 接口。C# 遵循定义 ICloneable 接口的相同路线。在我看来,克隆是 OOP 定义的一部分。但我想知道,为什么要创建这些接口,而复制构造函数似乎已被删除?

当我想到它时,我想到如果需要复制类型未知的对象(如引用基类型),复制构造函数将没有用。这似乎合乎逻辑。但我想知道是否还有其他我不知道的原因,Cloneable 接口比复制构造函数更受青睐?

4

6 回答 6

18

我认为这是因为在 Java 和 C# 中对于引用类型没有这种固有的复制构造函数需求。在 C++ 中,对象被命名。您可以(并且您通常会)复制(并且在 C++1x 中移动)它们,例如从函数返回时,因为返回指针需要您分配动态内存,这将是缓慢且难以管理的。语法是 T(x) 所以让构造函数接受 T 引用是有意义的。C++ 无法创建克隆函数,因为这需要再次按值返回对象(因此需要另一个副本)。

但是在 Java 中,对象是未命名的。只有对它们的引用,可以复制,但不能复制对象本身。对于您实际需要复制它们的情况,您可以使用 clone 调用(但我在其他答案中读到 clone 是有缺陷的。我不是 java 程序员,所以我不能对此发表评论)。由于返回的不是对象本身,而是对它的引用,因此克隆函数就足够了。还可以覆盖克隆功能。这不适用于复制构造函数。顺便说一句,在 C++ 中,当您需要复制多态对象时,clone也需要一个函数。它有一个名字,即所谓的虚拟拷贝构造函数

于 2008-12-23T06:49:53.737 回答
4

因为 C++ 和 Java(和 C#)不是一回事。C++ 没有内置接口,因为接口不是语言的一部分。您可以使用抽象类来伪造它们,但它们不是您对 C++ 的看法。此外,在 C++ 中,赋值通常很深。

在 Java 和 C# 中,赋值只涉及将句柄复制到内部对象。基本上当你看到:

SomeClass x = new SomeClass();

在 Java 或 C# 中,有一个 C++ 中不存在的内置间接级别。在 C++ 中,您编写:

SomeClass* x = new SomeClass();

C++ 中的赋值涉及取消引用的值:

*x = *another_x;

在 Java 中,您可以访问“真实”对象,因为没有像 *x 这样的取消引用运算符。所以要做一个深拷贝,你需要一个函数:clone()。Java 和 C# 都将该函数封装到一个接口中。

于 2008-12-23T06:42:53.343 回答
3

复制构造函数没有解决最终类型和通过超类级联克隆操作的问题 - 它们不可扩展。但是 Java 克隆机制也被广泛认为是严重损坏的;尤其是子类没有实现 clone(),而是从实现可克隆的超类继承的问题。

我强烈建议您仔细研究克隆,无论您选择什么路径 - 您可能会选择 clone() 选项,但请确保您确切知道如何正确执行。它很像 equals() 和 hashCode() - 表面上看起来很简单,但它必须完全正确地完成。

于 2008-12-23T06:40:59.247 回答
2

我认为你没有得到正确的观点。我给你我的两分钱。

根本上存在一个问题:在不知道确切的类类型的情况下创建一个类的克隆。如果您使用复制构造函数,则不能。

这是一个例子:

class A {
public A(A c) { aMember = c.aMember }
    int aMember;
}

class B : A {
public B(B c) : base(c) { bMember = c.bMember }
    int bMember;
}

class GenericContainer {
public GenericContainer(GenericContainer c) {
    // XXX Wrong code: if aBaseClass is an instance of B, the cloned member won't 
    // be a B instance!
    aBaseClass = new A(c.aBaseClass);
}
    A aBaseClass;
}

如果声明为 virtual,则 Clone 方法可以创建泛型成员的正确类实例。

这个问题在每种语言中都很常见,C# C++ 或 Java...

也许这就是你的意思,但我无法从任何答案中理解这一点。

于 2009-12-19T11:09:16.907 回答
1

只是想补充一点,在 Java 中,复制构造函数并非完全没用。

在某些情况下,您的类有一个可变非最终类型的私有实例变量,例如Date,并且有一个用于该变量的 setter 和 getter。在 setter 中,您应该复制给定日期,因为调用者可以稍后修改它,从而操纵对象的内部状态(通常是偶然的,但也可能是故意的)。在吸气剂中,需要同样的预防措施。

防御性副本可以通过调用来实现clone()(该类Date是可克隆的),但是恶意调用者可以使用其子类调用 setter,该子类使用Date覆盖该clone()方法{return this;},因此调用者可能仍然能够操纵您的对象。这就是复制构造函数发挥作用的地方:通过调用new Date(theDate),您一定会得到一个Date与给定日期具有相同时间戳的新实例,而这两个日期实例之间没有任何联系。在 getter 中,您可以使用 clone 方法,因为您知道私有变量将属于 class Date,但为了保持一致性,通常也使用复制构造函数。

另请注意,如果Date类是最终的(调用clone()是安全的)或不可变的(不需要复制),则需要复制构造函数。

于 2011-04-02T17:40:03.230 回答
0

我认为这只是因为一旦你定义了一个复制构造函数,你就再也不能传递引用本身了。(除非它有一个函数可以做到这一点……但这并不比使用 clone() 方法更容易。)在 C++ 中这不是问题:您可以传递整个对象或其引用。

于 2010-01-11T04:14:52.660 回答