C++中的拷贝构造函数跟Java中的对象克隆(clone)是一样的,它们的目的都是通过一个类的实例来获取它的一个副本或者叫拷贝,这个副本或拷贝跟原来的对象拥有相同的数据成员。
类名(类名& 实例名)
拷贝构造函数的实现可以在类外部实现也可以在是内联函数。
下面给出一个简单的拷贝构造函数的例子:
#include <iostream>
using namespace std;
class A
{
};
A::A(int i) :n(i) {}
A::A(A& a)
{
}
int main()
{
}
打印结果:
copy constructor is called!
copy constructor is called!
5
5
1.当用一个类的实例去初始化该类的另一个实例时,或者是给该类的另一个实例赋值时系统会调用拷贝构造函数。
2.当函数的形参是类的实例,在调用这个函数进行形参和实参结合时,系统会调用拷贝构造函数。
3.当函数的返回值为类的实例,函数调用结束返回时,系统会调用拷贝构造函数。
继续上面的例子:
void getValue(A a)
{
}
int main()
{
}
分析:这种情况会调用拷贝构造函数是因为在参数传递过程中,函数需要对参数建立副本。建立副本的过程相当于建立了一个新的实例,并且用参数中的实例来对其进行初始化。
A createA()
{
}
int main()
{
}
分析:在createA函数中创建了一个局部变量,当函数调用结束后,这个局部变量会消亡(会被系统回收),函数的局部变量无法在主调函数中继续生存。对于实例,也是如此。为了将函数中的返回值带回主调函数,编译系统会建立临时的无名实例,以便在主调函数中给其他实例赋值。在建立无名实例时,就是建立了一个新的实例,并且用局部函数中的返回对象对其进行初始化,所以调用了拷贝构造函数。在main函数中,用a去接收这样一个实例,相当于第一种情况,即用一个实例去初始化另一个实例,此时,也要调用拷贝构造函数,也就是说,一共要调用两次拷贝构造函数。(按道理来讲应该是这样的,但是我在VS2008中测试结果却不是这样的,只有在函数返回的时候调用了一次拷贝构造函数,在main函数里初始化a的时候没有调用拷贝构造函数)
默认拷贝构造函数
浅拷贝和深拷贝
1.默认的拷贝构造函数无法满足开发者对视力复制细节控制的要求。默认的拷贝构造函数是将实例中的所有非静态成员完全拷贝,但是,如果我现在不需要将所有的非静态成员完全拷贝,而只是希望拷贝一部分呢?这就要求拷贝构造函数能够更灵活,更细致,这是默认的拷贝构造函数无法做到的,所以需要为每个类定义自己的拷贝构造函数。
2.默认的拷贝构造函数无法对实例的资源进行拷贝(如动态内存等)。如果在实例中的数据成员拥有资源,拷贝构造函数只会建立该数据成员的一个拷贝,而不会自动为其分配资源,这样两个实例中就会拥有同一个资源。这样的局面显然是不合理的,不仅不符合对实例的要求,而且在析构函数中会被释放两次资源,导致程序错误。下面就是一个利用默认的拷贝构造函数导致程序出错的例子。
#include <iostream>
using namespace std;
class A
{
public:
private:
};
A::A(int i, int j) : n(i), p(&j) {}
A::~A()
{
}
int main()
{
}
分析:如果在VS2008下面测试的话,会发现只能打印出一个destructor is called!,然后程序就终止了,原因是对同一个资源释放了两次。在A的构造函数中,n被赋值为一个整形变量3,p被赋值为指向变量4的指针(假设p的值为0x1001)。当A a2 = a1时,会调用A的拷贝构造函数,因为我们并没有定义拷贝构造函数,因此,系统为我们添加了一个默认的拷贝构造函数,这个拷贝构造函数会将对象的所有非静态成员完全拷贝,也就是说这个时候a2中的n也是3,p也是0x1001,即a2中的p和a1中的p指向同一个变量。在main方法结束前,要将所有的栈对象全部析构,因此,会首先析构a1,在析构a1的时候同时释放了a1中的p所指向的那块资源,也就是整形变量4所占的4个字节的空间也同时被系统回收了。当析构a2的时候,同样会调用a2的析构函数,同时去析构a2中的p所指向的那块资源,因为a1中的p和a2中的p指向的是同一块资源并且a1中的p所指向的那块资源已经被析构掉了,因此,在析构a2时就会重复析构一个已经析构掉了的资源,这时系统就会报错。
修正这个bug很简单,就是给类A添加一个自定义的拷贝构造函数。代码如下:
A::A(A& a)
{
}
这样,a1和a2的p就分别持有自己的资源了,即使析构了a1,也不会析构掉a2中的p。
小结:当对实例进行拷贝时,未对实例的资源进行拷贝的过程称为浅拷贝。对于浅拷贝所带来的弊端,可以通过自定义拷贝构造函数来解决。当对实例进行拷贝时,对实例的资源也进行拷贝的过程称为深拷贝。对于需要拷贝构造函数进行深拷贝的并不止堆内存,对文件的操作、系统设备的占有(如计算机端口、打印)等都需要进行深拷贝,一般来讲,需要在析构函数中手动析构的所有成员(比如指针、引用等)都需要在拷贝构造函数中对其重新分配资源。