虽然说拷贝构造函数对于基本的C++编程来说,可能不太用得着, 不过这并不说明拷贝构造函数没什么用,它也能为我们解决一些我们常常忽略的问题:
假设现在有一个Student类,有一个数据成员是char *name;
现在有一个对象Student stu1("yang") ; //数据成员name="yang";
再生成一个对象,stu2,把stu1拷贝给stu2,即Student stu2(stu1);
如果我们没有自己写拷贝构造函数,那么编译器会调用默认的拷贝构造函数,进行浅拷贝,
浅拷贝的意思是内存空间中只有一份"yang",对象stu1的name指针指向了它,把stu1拷贝给stu2,stu2的name也指向了那块内存空间,stu1和stu2共享一块内存空间,这样就会出现一个问题,当stu1对象被销毁后,对应的stu1的name指向的"yang"页被销毁了,可是stu2也指向它,所以stu2就变成了空, 甚至是乱码.
来看一个例子:
#include<iostream> using namespace std; class Array{ public: int size; int *data; Array(int size):size(size),data(new int[size]){} ~Array(){ delete[] this->data; } }; int main(int argc,char *argv[]) { Array first(20); first.data[0]=25; { Array copy=first; cout << first.data[0] << " "<<copy.data[0] << endl; }(1) first.data[0]=10;(2) return 0; }
运行结果:
25 25 *** Error in `./a.out': double free or corruption (fasttop): 0x0000000001821010 *** 已放弃 (核心已转储)
为什么会出现我们最头疼的段错误呢?
根本原因跟前面哪个例子一样,我们没有写拷贝构造函数, 因此,编译器生成一个默认拷贝构造函数,默认拷贝构造函数如下:
Array(const Array& other) : size(other.size), data(other.data) {}
它对data指针执行的是浅拷贝,也就是说,他只拷贝了data成员的地址,这就带来了两个指针共享对象指向同一段内存的风险.
当程序执行到(1)处时,copy的析构函数会被调用,删除了copy的data,由于first和copy对象共享一个data指针,因此执行到(2)处时,由于内存空间被释放,而引起的段错误,
所以,必须自己来写一个拷贝构造函数,并执行深拷贝.
Array(const Array& other) : size(other.size), data(new int[other.size]) { std::copy(other.data, other.data + other.size, data); }
什么是浅拷贝和深拷贝?
浅拷贝:仅仅逐个成员拷贝,而不拷贝资源的方式;浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象,比较典型的就有Reference(引用)对象,如Class(类)。
深拷贝:既拷贝成员,又拷贝资源的方式。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。
系统提供的默认拷贝构造函数为浅拷贝,深拷贝必须自己定义。
这也解释了上段代码中段错误原因及为什么要自己写拷贝构造函数.
什么是拷贝构造函数?
拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其唯一的参数(对象的引用)是不可变的(const类型)。也就是说,当用一个已经存在的对象为一个新的对象进行赋值时,首先要给新对象的数据成员分配空间资源以创建新对象,然后用源对象的成员值进行初始化。这个行为必须在对象构造的时候进行完成,而普通的构造函数无法完成这项工作,因此拷贝构造函数应运而生!
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。
1). 一个对象以值传递的方式传入函数体
2). 一个对象以值传递的方式从函数返回
3). 一个对象需要通过另外一个对象进行初始化时。
以上的情况需要拷贝构造函数的调用。如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作赋共同实现的。
总结一下:
1) 拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。
2)如果不显式声明拷贝构造函数的时候,编译器也会生成一个默认的拷贝构造函数,只执行浅拷贝在一般的情况下,浅拷贝运行的也很好。
但是在遇到类有指针数据成员时就出现问题了:因为默认的拷贝构造函数是按成员拷贝构造,这导致了两个不同的指针(如ptr1=ptr2)指向了相同的 内存。当一个实例销毁时,调用析构函数free(ptr1)释放了这段内存,那么剩下的一个实例的指针ptr2就无效了,在被销毁的时候free(ptr2)就会出现错误了, 这相当于重复释放
一块内存两次。这种情况必须显式声明并实现自己的拷贝构造函数,来为新的实例的指针分配新的内存。