三五法则以及行为像值的类和行为像指针的类

来源:互联网 发布:微信墙用什么软件 编辑:程序博客网 时间:2024/05/22 01:26

转载链接:http://blog.csdn.net/ysayk/article/details/51056431#comments

文章作者:XDmonkey


首先我们说明有三个基本操作可以控制类的拷贝操作:拷贝构造函数、拷贝赋值运算符(这两个的区别之前有文章讲到)、以及析构函数。

  那么我们什么时候需要自己构建拷贝构造函数和重载拷贝赋值运算符呢,在三/五法则中给出了自己构建拷贝构造函数和重载拷贝赋值运算符的充分条件。

  三/五法则的内容,当我们需要决定一个类是否要定义它自己版本的拷贝控制成员的时候,一个基本原则就是首先确定这个类是否需要我们自己手动定义一个析构函数,通常,对析构函数的需求要比对拷贝构造函数或赋值运算符的需求更加的明显。简单来说需要手动构造析构函数是需要手动构造拷贝构造函数和重载赋值运算符的充分条件。为什么这么说呢?

  举例如下:

[cpp] view plain copy
  1. //如果我们为HasPtr定义了一个析构函数,但使用合成版本的拷贝构造函数和拷贝赋值函数运算符,考虑会发生什么?  
  2. class HasPtr{  
  3.     public:  
  4.         HasPtr(string &s=string()):ps(new) string(s),i(0){}  
  5.         ~HasPtr(){delete ps;}  
  6. }   
  自然,在这里我们使用的合成版本的拷贝构造函数和拷贝赋值函数是看不见的,但是实际上默认的函数并没有开辟新的内存,所以这样导致的结果是多个对象会指向相同的内存,这样当一个对象进行析构的时候,其他的对象指向的对象就会指向一个空的位置,而且甚至还会对于一个根本不存在的位置进行delete,这样对于同一个指针进行delete两次,这样的行为是未定义的。

  那么一般再这个时候,我们就必须自己来定义拷贝构造函数和拷贝赋值运算符号了,同时,在这个时候我们也有两种选择,一种是使类表现的像值一样,每个对象都保留有自己对应的副本,另一种选择就是让类表现得像指针一样,在后面这种情况中,我们为了避免出现上面这种连续delete两次的情况,就需要像智能指针一样定义一个use来记录使用次数。

  行为像值的类和行为像指针的类

  首先解释行为像值,对于行为像值,实际上就是对于每一个已经实例化的类的对象,对于每一个类管理的资源(这里C++primer中是针对于可以动态分配内存的容器而言的,实际上应该所有非内置类型需要动态申请的类型都需要这么做,暂时是只想到),当对象与对象之间进行复制操作的时候,每个对象都保存有一份副本,使得当有一个对象中对应的成员占有的空间被释放的时候,对应赋值过的对象中的相应成员不受影响。实际上这样释放空间的过程不应该仅仅存在于析构函数中,在我们重载赋值操作符(=)的时候也应该有释放被赋值的对象原来占有的类外空间的步骤,在c++primer P453中以string举例的话,还可以注意得到,为了避免自身向自身赋值的情况这里用了一个newp变量来临时进行存储,有点类似于原来讲swap函数的时候其中temp的意思,但是这里也要注意,因为每一个实例化的对象中都要存储一份对应的成员的话,就不能像行为像指针的类中赋值操作的那样直接指针等于指针的方式,因为在行为像值的类中是有专门的计数器的,而在行为像值的类中是没有的,所以不能用同样的指针,必须要开辟新的地址。

  C++primer中的例子如下:

[cpp] view plain copy
  1. HasPtr & HasPtr::operator=(const HasPtr &rhs)  
  2. {  
  3.     auto newp= new string(*rhs.ps)//进行备份,且这是一个复制过程  
  4.         delete ps;//对被赋值放原来的资源进行释放  
  5.     ps =newp;  
  6.     i=rhs.i;  
  7.     return *this;  
  8. }  
  9. class HasPtr {  
  10. public:  
  11.     HasPtr(const string &s=string()):ps(new string(s),)i(0){}  
  12.     HasPtr(const HasPtr &p):ps(new string(*p.ps)),i(p.i){}//复制构造函数(本质上),即使是构造函数,依旧new出了空间,这里是和合成得复制构造函数  
  13.     HasPtr& operator=(const HasPtr &);  
  14.     ~HasPtr(){delete ps;}//析构函数中同样加入了这样一个过程  
  15. private:  
  16.     string *ps;  
  17.     int i;   
  18. };  

  对于想要使类的行为像指针的话,就需要加入新的计数机制,解决的问题就是如果类中有上述所说的默认析构函数不能释放的资源的时候需要程序员自己决定什么时候进行资源的释放。那么这个时候的机制就有点类似于智能指针了,我们需要在类中加入一个私有成员用于引用计数。那么按照以上定义的话,行为像指针的类应该像下面这样进行定义。

[cpp] view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. class HasPtr{  
  5.     public:  
  6.         HasPtr(const string &s=string())://有string参数就执行string参数  
  7.             ps(new string(s)),i(0),use(new size_t(1)){}  
  8.         HasPtr(const HasPtr &p):ps(p.ps),i(p.i),use(p.use){++*use;}  
  9.         HasPtr& operator =(const HasPtr&);  
  10.         ~HasPtr();  
  11.     private:  
  12.         string *ps;  
  13.         int i;  
  14.         size_t *use;//use一定要为指针类型  
  15. };  
  16.     HasPtr::~HasPtr(){//析构函数,注意一点,自动调用析构函数的时候需要*use的值为1,也就是没有其他的对象指向这一块内存了  
  17.         if(--*use==0)  
  18.             delete ps;  
  19.             delete use;  
  20.     }  
  21.     HasPtr& HasPtr::operator=(const HasPtr &rhs){  
  22.         ++*rhs.use;  
  23.         if(--*use==0){//如果先进行减*use并释放内存的操作的话,那么就无法去处理自我赋值的情况了。  
  24.             delete ps;  
  25.             delete use;  
  26.         }  
  27.         ps=rhs.ps;  
  28.         i=rhs.i;  
  29.         use=rhs.use;  
  30.         return *this;          
  31.     }  
  32.     int main(){  
  33.         return 0;  
  34. }