赋值复制构造函数



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

通过覆盖=来使用复制构造函数的概念在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

=操作符也做同样的事情:将对象赋值给变量。它不产生开销。在运行时不同的是构造函数调用。

复制构造函数允许你保留两个引用;一个指向"旧"对象,一个指向"新"对象。这些对象是独立的(或者应该取决于您允许复制的深度)

如果执行重赋值操作,则只有对"new"对象的引用。"旧"对象将不再可访问(假设没有其他对它的引用),并且将有资格进行垃圾收集。

归根结底是你想要达到的目标。如果需要对象的精确副本,并且希望该对象具有独立的生命周期,请使用复制构造函数。如果你只想要一个新对象,而不关心旧对象,请重新分配变量。

PS -我必须承认,我没有读你链接的问题…

首先是关于c++和Java中拷贝构造和拷贝赋值的一些基础知识

由于c++中的对象语义和Java中的引用语义,c++和Java是两种非常不同的东西。我的意思是:
SomeClass obj = expr;

在c++中,这一行表示一个用expr初始化的新的对象。在Java中,这一行不是创建一个新对象,而是创建一个指向对象的新引用,该引用引用表达式给出的内容。Java引用可以是空的,意思是"没有对象"。c++对象,所以不存在"无对象"——object;-) Java引用非常像c++指针。唯一使这种区别变得困难的是,c++有指针对象,并使用->解引用指针,而在Java中,所有都是引用(除了int和其他一些基本类型),通过引用访问对象使用.,这很容易与c++中对"直接"对象的访问混淆。"一切都是引用"意味着任何对象(除了int &

说到这里,让我们来看看两种语言的赋值和拷贝。

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

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=,它(通常)将一个对象的值赋给一个已经存在的对象,丢弃你所赋给的对象的旧值。如果您不自己定义它,编译器最好为您生成一个,对对象的元素进行简单的元素复制。在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之后,您将拥有objAobjB在分配之前的值,而objB已经更改。
在Java(第2行)中,该赋值是引用的赋值,这意味着之后两个引用objAobjB引用了同一个对象,而之前在objA中引用的对象不再被引用,因此它将被垃圾收集。调用objB.change()将更改两个引用所指向的单个对象,通过引用objA访问它将显示这些更改。
这与c++指针(几乎)相同。你看,你无法区分对象和指针赋值的语法,它们都是由被赋值的类型决定的。与c++的不同之处在于它没有垃圾收集器,你最终会出现内存泄漏,因为ptrObjA指向的对象不能再被删除了。

关于你的问题:考虑一个c++类:
class X {
int* pi;
unsigned count;
public:
X(X const&);
X& operator= (X const&);
~X();
};

假设每个X对象分配它自己的动态int数组,指向它的指针存储在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
}

现在看看你问题中的那句话:考虑两个对象x1和x2以及赋值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开始,它就变成了"五法则",因为你必须同时考虑move赋值和move构造。

最新更新