复制构造函数而不是赋值

时间:2013-06-21 06:36:53

标签: java c++ oop constructor

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

3 个答案:

答案 0 :(得分: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

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

答案 1 :(得分:3)

复制构造函数允许您保留两个引用;一到#34;老"对象,一个到#34; new"。这些对象是独立的(或者应该取决于您允许复制的深度)

如果你进行了重新分配,你只能引用" new"宾语。 " old"对象将不再可访问(假设没有其他引用)并且有资格进行垃圾收集。

归结为你想要实现的目标。如果您想要对象的精确副本,并且希望此对象具有自己的独立生命,请使用复制构造函数。如果您只是想要一个新对象并且不关心旧对象,请重新分配该变量。

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

答案 2 :(得分:1)

首先介绍一些关于C ++和Java中的复制构造和复制赋值的基础知识

由于C ++中的对象语义和Java中的Reference语义,C ++和Java是两种截然不同的动物。我的意思是:

SomeClass obj = expr;

在C ++中,此行表示使用expr初始化的新对象。在Java中,此行不创建新对象,而是创建对象的新引用,该引用引用表达式提供的内容。 Java引用可以为null,表示"没有对象"。 C ++对象,因此没有"没有对象" -object ;-) Java引用非常类似于C ++指针。唯一可以区分困难的是,虽然C ++有指针对象并且用->取消引用指针,但在Java 中,所有都是引用(除了int)以及一些其他基本类型),并通过引用访问对象使用.,这很容易与访问"直接" C ++中的对象。 "一切都是参考"意味着,任何对象(int& Co.除外)都是在概念上在堆上创建的。

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

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

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

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

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

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=(通常)将对象的值分配给现有对象,丢弃您指定的对象的旧值至。如果你自己没有定义它,编译器最好为你生成一个,做一个简单的元素副本的对象'元素。在Java中,您不能重载运算符,因此您必须定义一个方法,例如, 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的值为objB,但objB已更改。objA已更改。objB已更改。 在Java(第2行)中,赋值是引用的赋值,意味着之后两个引用objAobjB.change()引用同一个对象,而之前引用的对象ba objA是不再提及,所以它将被垃圾收集。调用ptrObjA将更改两个引用指向的单个对象,并通过引用class X { int* pi; unsigned count; public: X(X const&); X& operator= (X const&); ~X(); }; 访问它将显示这些更改。
它再次与C ++指针(几乎)相同。您看到无法区分对象和指针赋值的语法,它们都是由分配的类型决定的。与C ++的区别在于它没有garbace收集器,并且最终导致内存泄漏,因为指向的对象pi不能再被删除。

关于您的问题:

考虑一个C ++类:

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

假设每个X对象都分配了它自己的动态int数组,指向它的指针存储在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 } 中。由于C ++没有垃圾收集,X对象必须关心自己分配的内存,即他们必须手动销毁它:

x1 = x2

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

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

现在问你的qoute: 考虑两个对象x1和x2以及赋值x1.pi。如果将everythign留给编译器,它将生成一个赋值运算符,如下所示:

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 } 。就像我在关于复制赋值的部分中解释的那样,这将导致两个指针指向同一个数组,并且先前由{{1}}拥有的数组将在空间中丢失,这意味着当两个对象都有泄漏和奇怪的行为在他们的共享阵列上工作 正确的实施将是:

{{1}}

在这里,您可以看到引用的内容:首先,对象是"清除",即释放内存,基本上执行析构函数的操作("实例必须被销毁" )。 然后,执行深层复制,执行复制构造函数的操作(" ...并重新初始化")。

这被称为"三个规则":如果你必须编写自己的复制构造函数(因为生成的复制构造函数不是你想要它做的),你将大部分必须自己编写析构函数和赋值运算符。从C ++ 11开始,它已成为"五个规则",因为你必须考虑移动分配和移动构造。