源自《C++ primer》第四版 p419

 
包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。
 
设计具有指针成员的类时,类设计者必须首先需要决定的是该指针应提供什么行为。将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。
 
指针成员默认具有与指针对象同样的行为。然而,通过不同的复制控制策略,可以为指针成员实现不同的行为。大多数 C++ 类采用以下三种方法之一管理指针成员:
 
1、指针成员采取常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制。
2、类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针。
3、类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。
 
本节中介绍三个类,分别实现管理指针成员的三种不同方法。
 
一个带指针成员的简单类
 
为了阐明所涉及的问题,我们将实现一个简单类,该类包含一个 int 值和一个指针:
 
  
  1. // class that has a pointer member that behaves like a plain pointer 
  2.      class HasPtr { 
  3.      public
  4.          // copy of the values we're given 
  5.          HasPtr(int *p, int i): ptr(p), val(i) { } 
  6.          // const members to return the value of the indicated data member 
  7.          int *get_ptr() const { return ptr; } 
  8.          int get_int() const { return val; } 
  9.          // non const members to change the indicated data member 
  10.          void set_ptr(int *p) { ptr = p; } 
  11.          void set_int(int i) { val = i; } 
  12.          // return or change the value pointed to, so ok for const objects 
  13.          int get_ptr_val() const { return *ptr; } 
  14.          void set_ptr_val(int val) const { *ptr = val; } 
  15.      private
  16.          int *ptr; 
  17.          int val; 
  18.      }; 
HasPtr 构造函数接受两个形参,将它们复制到 HasPtr 的数据成员。HasPtr 类提供简单的访问函数:函数 get_int 和 get_ptr 分别返回 int 成员和指针成员的值:set_int 和 set_ptr 成员则使我们能够改变这些成员,给 int 成员一个新值或使指针成员指向不同的对象。还定义了 get_ptr_val 和 set_ptr_val 成员,它们能够获取和设置指针所指向的基础值。
 
1、默认复制/赋值与指针成员
 
因为 HasPtr 类没有定义复制构造函数,所以复制一个 HasPtr 对象将复制两个成员:
 
  
  1. int obj = 0; 
  2. HasPtr ptr1(&obj, 42); // int* member points to obj, val is 42 
  3. HasPtr ptr2(ptr1);     // int* member points to obj, val is 42 
复制之后,ptr1 和 ptr2 中的指针指向同一对象且两个对象中的 int 值相同。但是,因为指针的值不同于它所指对象的值,这两个成员的行为看来非常不同。复制之后,int 值是清楚和独立的,而指针则纠缠在一起。可能出现悬垂指针。
 
2、智能指针类
 
另一种方法是定义所谓的智能指针类。智能指针除了增加功能外,其行为像普通指针一样。本例中让智能指针负责删除共享对象。用户将动态分配一个对象并将该对象的地址传给新的 HasPtr 类。用户仍然可以通过普通指针访问对象,但绝不能删除指针。HasPtr 类将保证在撤销指向对象的最后一个 HasPtr 对象时删除对象。HasPtr 在其他方面的行为与普通指针一样。具体而言,复制对象时,副本和原对象将指向同一基础对象,如果通过一个副本改变基础对象,则通过另一对象访问的值也会改变。新的 HasPtr 类需要一个析构函数来删除指针,但是,析构函数不能无条件地删除指针。如果两个 HasPtr 对象指向同一基础对象,那么,在两个对象都撤销之前,我们并不希望删除基础对象。为了编写析构函数,需要知道这个 HasPtr 对象是否为指向给定对象的最后一个。
 
引入使用计数
 
定义智能指针的通用技术是采用一个使用计数。智能指针类将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为 0 时,删除对象。使用计数有时也称为引用计数。每次创建类的新对象时,初始化指针并将使用计数置为 1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至 0,则删除对象),并增加右操作数所指对象的使用计数的值。最后,调用析构函数时,析构函数减少使用计数的值,如果计数减至 0,则删除基础对象。
 
  
  1. int obj; 
  2. HasPtr p1(&obj, 42); 
  3. HasPtr p2(p1);  // p1 and p2 both point to same int object 
  4. HasPtr p3(p1);  // p1, p2, and p3 all point to same int object 
使用计数类
 
  
  1. class U_Ptr { 
  2.          friend class HasPtr; 
  3.          int *ip; 
  4.          size_t use; 
  5.          U_Ptr(int *p): ip(p), use(1) { } 
  6.         ~U_Ptr() { delete ip; } 
  7. }; 
图示如下:
如果复制这个对象,则如下图:
完整代码如下:
 
  
  1. //private class for use by HasPtr only 
  2. class U_Ptr { 
  3.     friend class HasPtr; 
  4.     int *ip; 
  5.     size_t use; 
  6.     U_Ptr(int *p): ip(p), use(1) { } 
  7.     ~U_Ptr() { delete ip; } 
  8. }; 
  9.   
  10. class HasPtr { 
  11. public
  12.     // HasPtr owns the pointer; p must have been dynamically allocated 
  13.     HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i) { } 
  14.     // copy members and increment the use count 
  15.     HasPtr(const HasPtr &orig): 
  16.        ptr(orig.ptr), val(orig.val) { ++ptr->use; } 
  17.     HasPtr& operator=(const HasPtr&); 
  18.     // if use count goes to zero, delete the U_Ptr object 
  19.     ~HasPtr() { if (--ptr->use == 0) delete ptr; } 
  20.  
  21.     friend ostream& operator<<(ostream&, const HasPtr&); 
  22.     // copy control and constructors as before 
  23.  
  24.     // accessors must change to fetch value from U_Ptr object 
  25.     int *get_ptr() const { return ptr->ip; } 
  26.     int get_int() const { return val; } 
  27.  
  28.     // change the appropriate data member 
  29.     void set_ptr(int *p) { ptr->ip = p; } 
  30.     void set_int(int i) { val = i; } 
  31.  
  32.     // return or change the value pointed to, so ok for const objects 
  33.     // Note: *ptr->ip is equivalent to *(ptr->ip) 
  34.     int get_ptr_val() const { return *ptr->ip; } 
  35.     void set_ptr_val(int i) { *ptr->ip = i; } 
  36.  
  37. private
  38.     U_Ptr *ptr;        // points to use-counted U_Ptr class 
  39.     int val; 
  40. }; 
  41.  
  42. HasPtr& HasPtr::operator=(const HasPtr &rhs) 
  43.     ++rhs.ptr->use;     // increment use count on rhs first 
  44.     if (--ptr->use == 0) 
  45.          delete ptr;    // if use count goes to 0 on this object, delete it 
  46.     ptr = rhs.ptr;      // copy the U_Ptr object 
  47.     val = rhs.val;      // copy the int member 
  48.     return *this
3、定义值类型
 
处理指针成员的另一个完全不同的方法,是给指针成员提供值语义:
 
 
  
  1. class HasPtr { 
  2. public
  3.     // no point to passing a pointer if we're going to copy it anyway 
  4.     // store pointer to a copy of the object we're given 
  5.     HasPtr(const int &p, int i): ptr(new int(p)), val(i) {} 
  6.     // copy members and increment the use count 
  7.     HasPtr(const HasPtr &orig): 
  8.        ptr(new int (*orig.ptr)), val(orig.val) { } 
  9.  
  10.     HasPtr& operator=(const HasPtr&); 
  11.     // wrong: don't define a destructor without also defining copy and assign 
  12.     ~HasPtr() { delete ptr; } 
  13.     friend ostream& operator<<(ostream&, const HasPtr&); 
  14.     // accessors must change to fetch value from Ptr object 
  15.     int get_ptr_val() const { return *ptr; } 
  16.     int get_int() const { return val; } 
  17.  
  18.     // change the appropriate data member 
  19.     void set_ptr(int *p) { ptr = p; } 
  20.     void set_int(int i)  { val = i; } 
  21.  
  22.     // return or change the value pointed to, so ok for const objects 
  23.     int *get_ptr() const { return ptr; } 
  24.     void set_ptr_val(int p) const { *ptr = p; } 
  25.  
  26. private
  27.     int *ptr;        // points to an int 
  28.     int val; 
  29. }; 
  30.  
  31. HasPtr& HasPtr::operator=(const HasPtr &rhs) 
  32.     // Note: Every HasPtr is guaranteed to point at an actual int; 
  33.     //       We know that ptr cannot be a zero pointer 
  34.     *ptr = *rhs.ptr;      // copy the value pointed to 
  35.     val = rhs.val;        // copy the int 
  36.     return *this