0

经过大量搜索,至少这个问题帮助我理解了使用复制构造函数和赋值运算符的区别
我的问题是关于这一行
instance has to be destroyed and re-initialized if it has internal dynamic memory
如果我初始化一个实例
Object copyObj = null;,然后赋值,copyObj = realObj那么这个开销(销毁和重新初始化)仍然存在?
如果不是,那么现在在这种情况下,我为什么要使用 Copy Constructor 而不是直接分配对象

4

3 回答 3

5

=Java 中不存在通过简单地覆盖来使用复制构造函数的概念。您不能覆盖运算符。Java 中复制构造函数的概念是这样工作的:

public class MyType {

    private String myField;

    public MyType(MyType source) {
        this.myField = source.myField;
    }
}

复制构造函数是一个构造函数,它接受相同类型的参数并复制它的所有值。它用于获取具有相同状态的新对象。

MyType original = new MyType();
MyType copy = new MyType(original);
// After here orginal == copy will be false and original.equals(copy) should be true
MyType referenceCopy = original
// After here orginal == referenceCopy will be true and original.equals(referenceCopy) will also be true

运算符的=作用相同:将对象分配给变量。它不会产生任何开销。运行时可能不同的是构造函数调用。

于 2013-06-21T06:45:08.103 回答
3

Copy 构造函数允许您保留两个引用;一个到“旧”对象,一个到“新”对象。这些对象是独立的(或者应该取决于您允许副本的深度)

如果您进行重新分配,您只有对“新”对象的引用。“旧”对象将不再可访问(假设没有其他对它的引用)并且将有资格进行垃圾收集。

这取决于你想要达到的目标。如果您想要对象的精确副本,并且希望该对象拥有自己的独立生命,请使用复制构造函数。如果您只想要一个新对象而不关心旧对象,请重新分配变量。

PS-我不得不承认,我没有阅读您链接到的问题..

于 2013-06-21T06:44:59.483 回答
1

首先是关于 C++ 和 Java 中的复制构造和复制分配的一些基础知识

由于 C++ 中的对象语义和 Java 中的引用语义,C++ 和 Java 是两个截然不同的野兽。我的意思是:

SomeClass obj = expr;

在 C++ 中,这一行表示一个用 初始化的新对象expr。在 Java 中,这一行创建的不是新对象,而是对对象的新引用,并且该引用指的是表达式给出的任何内容。Java 引用可以为空,表示“没有对象”。C++ 对象,所以没有“无对象”-对象 ;-) Java 引用非常像 C++ 指针。唯一会使区分变得困难的是,虽然 C++ 有指针对象,并且用 取消引用指针->,但在 Java 中,一切都是引用(除了 int 和一些其他基本类型),通过引用访问对象使用.,很容易与在 C++ 中访问“直接”对象相混淆。“一切都是引用”意味着任何对象(除了 int & Co.)在概念上都是在堆上创建的。

话虽如此,让我们看看两种语言的作业和副本。

复制构造在两种语言中的含义相同,本质上是创建一个新对象,该对象是另一个对象的副本。复制构造函数定义类似:

SomeClass(SomeClass obj) { /* ... */ }         //Java
SomeClass(SomeClass const& obj) { /* ... */ }  //C++

不同之处仅在于 C++ 必须将参数显式声明为引用,而在 Java 中,一切都是引用。用 C++ 编写第一行将定义一个构造函数,该构造函数通过 copy获取它的参数,即编译器必须已经使用复制构造函数创建一个副本,它必须为其创建一个副本,... - 不是一个好主意.

在两种语言中使用复制构造将如下所示:

SomeClass newObj = new SomeClass(oldObj);     //Java
SomeClass newObj = oldObj;                    //C++ object
SomeClass* ptrNewObj = new SomeClass(oldObj); //C++ pointer

当您查看第一行和第三行时,它们看起来基本相同。这是因为它们本质上是相同的,因为 Java 引用本质上就像 C++ 中的指针。这两个表达式都创建了一个新对象,该对象可以比创建它的函数作用域更长。第二行在堆栈上创建了一个普通的 C++ 对象,这在 Java 中不存在。在 C++ 中,副本也由编译器隐式创建,例如。当一个对象被传递给一个按值而不是按引用接受其参数的函数时。

定义复制分配:在 C++ 中,您可以定义operator=wich(通常)将对象的值分配给已经存在的对象,丢弃您分配的对象的旧值。如果您自己不定义它,编译器会尽力为您生成一个,对对象的元素进行简单的元素复制。在 Java 中,您不能重载运算符,因此您必须定义一个名为 eg 的方法assign

void assign(SomeObject other)                  {/* ... */} //Java
SomeObject& operator=(SomeObject const& other) {/* ... */} //C++

请再次注意,我们在 C++ 中明确声明参数为引用,但在 Java 中没有。

使用复制分配:

objA = objB;        //C++ copy assignment
objA = objB;        //Java ref assignment
ptrObjA = ptrObjB;  //C++ pointer assignment
objA.assign(objB);  //Java 
objB.change();

这里前两行看起来完全一样,但差别不大。请记住,在 C++ 中,objAobjB取消对象本身,而在 Java 中它们只是引用。因此,在 C++ 中,这是对对象的复制分配,这意味着您以两个具有相同内容的对象结束。更改后objB,您将拥有分配前的objA值,而 while已更改。 在 Java(第 2 行)中,赋值是引用的赋值,这意味着之后两个引用都引用了同一个对象,而之前引用的对象 ba不再被引用,因此它将被垃圾回收。打电话objBobjB
objAobjBobjAobjB.change()将更改两个引用指向的单个对象,通过引用访问它objA会显示这些更改。
同样,它(几乎)与 C++ 指针相同。您会看到您无法区分对象和指针分配的语法,这完全取决于分配的类型。与 C++ 的不同之处在于它没有垃圾收集器,并且由于ptrObjA指向的对象无法再删除,因此最终会导致内存泄漏。

关于你的问题:

考虑一个 C++ 类:

class X {
  int* pi;
  unsigned count;
public:
  X(X const&);
  X& operator= (X const&);
  ~X();
};

假设每个 X 对象分配它自己的动态整数数组,指向它的指针存储在pi. 由于 C++ 没有垃圾收集,X 对象必须关心自己分配的内存,即他们必须手动销毁它:

X::~X() { delete[] pi; }

复制构造函数将复制原始的动态数组,因此两者在使用同一个数组时不会冲突。这称为深拷贝,在 Java 和 C++ 中同样使用:

X::X(X const& other) : pi(NULL), count(0) {
  pi = new int[other.count];  //allocates own memory
  count = other.count;
  std::copy(other.pi, other.pi+count, pi); //copies the contents of the array
}

现在到问题中的 qoute:考虑两个对象 x1 和 x2 以及 assignment x1 = x2。如果将所有内容留给编译器,它将生成一个赋值运算符,如下所示:

X& X::operator=(X const& other) {
  pi = other.pi;
  count = other.count;
}

在第一行x1.pi中获取 的指针值x2.pi。就像我在有关复制分配的部分中解释的那样,这将导致两个指针都指向同一个数组,并且先前拥有的数组x1将在空间中丢失,这意味着当两个对象都在它们的共享数组上工作时,您会出现泄漏和奇怪的行为.
正确的实现是:

X& X::operator=(X const& other) {
  delete[] pi;           //1

  pi = new int[other.count];  //allocates own memory
  count = other.count;
  std::copy(other.pi, other.pi+count, pi); //copies the contents of the array
}

在这里您可以看到引用的内容:首先,对象被“清理”,即内存被释放,本质上是在做析构函数所做的事情(“实例必须被销毁”)。然后,执行深度复制,执行复制构造函数所做的事情(“...并重新初始化”)。

这被称为“三法则”:如果您必须编写自己的复制构造函数(因为生成的构造函数不是您想要的),那么您主要还必须编写自己的析构函数和赋值运算符。自 C++11 以来,它已成为“五法则”,因为您还必须考虑移动分配和移动构造。

于 2013-06-21T07:57:07.877 回答