转载链接:http://blog.csdn.net/ysayk/article/details/51056431#comments
文章作者:XDmonkey
首先我们说明有三个基本操作可以控制类的拷贝操作:拷贝构造函数、拷贝赋值运算符(这两个的区别之前有文章讲到)、以及析构函数。
那么我们什么时候需要自己构建拷贝构造函数和重载拷贝赋值运算符呢,在三/五法则中给出了自己构建拷贝构造函数和重载拷贝赋值运算符的充分条件。
三/五法则的内容,当我们需要决定一个类是否要定义它自己版本的拷贝控制成员的时候,一个基本原则就是首先确定这个类是否需要我们自己手动定义一个析构函数,通常,对析构函数的需求要比对拷贝构造函数或赋值运算符的需求更加的明显。简单来说需要手动构造析构函数是需要手动构造拷贝构造函数和重载赋值运算符的充分条件。为什么这么说呢?
举例如下:
自然,在这里我们使用的合成版本的拷贝构造函数和拷贝赋值函数是看不见的,但是实际上默认的函数并没有开辟新的内存,所以这样导致的结果是多个对象会指向相同的内存,这样当一个对象进行析构的时候,其他的对象指向的对象就会指向一个空的位置,而且甚至还会对于一个根本不存在的位置进行delete,这样对于同一个指针进行delete两次,这样的行为是未定义的。
那么一般再这个时候,我们就必须自己来定义拷贝构造函数和拷贝赋值运算符号了,同时,在这个时候我们也有两种选择,一种是使类表现的像值一样,每个对象都保留有自己对应的副本,另一种选择就是让类表现得像指针一样,在后面这种情况中,我们为了避免出现上面这种连续delete两次的情况,就需要像智能指针一样定义一个use来记录使用次数。
行为像值的类和行为像指针的类
首先解释行为像值,对于行为像值,实际上就是对于每一个已经实例化的类的对象,对于每一个类管理的资源(这里C++primer中是针对于可以动态分配内存的容器而言的,实际上应该所有非内置类型需要动态申请的类型都需要这么做,暂时是只想到),当对象与对象之间进行复制操作的时候,每个对象都保存有一份副本,使得当有一个对象中对应的成员占有的空间被释放的时候,对应赋值过的对象中的相应成员不受影响。实际上这样释放空间的过程不应该仅仅存在于析构函数中,在我们重载赋值操作符(=)的时候也应该有释放被赋值的对象原来占有的类外空间的步骤,在c++primer P453中以string举例的话,还可以注意得到,为了避免自身向自身赋值的情况这里用了一个newp变量来临时进行存储,有点类似于原来讲swap函数的时候其中temp的意思,但是这里也要注意,因为每一个实例化的对象中都要存储一份对应的成员的话,就不能像行为像指针的类中赋值操作的那样直接指针等于指针的方式,因为在行为像值的类中是有专门的计数器的,而在行为像值的类中是没有的,所以不能用同样的指针,必须要开辟新的地址。
C++primer中的例子如下:
对于想要使类的行为像指针的话,就需要加入新的计数机制,解决的问题就是如果类中有上述所说的默认析构函数不能释放的资源的时候需要程序员自己决定什么时候进行资源的释放。那么这个时候的机制就有点类似于智能指针了,我们需要在类中加入一个私有成员用于引用计数。那么按照以上定义的话,行为像指针的类应该像下面这样进行定义。