句柄类/智能指针(smart point)是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。句柄类实际上是通过复制指向引用计数器类型的指针,来代替复制实际对象;从而使得复制对象变成复制指针,实现虚拟复制(即是用复制对象的地址代替复制对象本身),以提高内存效率和访问速度。
引用计数是复制控制成员中使用的技术,跟踪该类有多少个对象共享一个指针。引用计数与共享对象一起存储,需要创建一个单独类指向共享对象并管理引用计数。由构造函数而不是赋值构造函数设置共享对象的状态并将引用计数置为1,每次有复制构造函数或赋值操作符生成新的副本时,引用计数加1,由析构函数撤销对象或作为赋值操作符的左操作数撤销对象时,引用计数减1,赋值操作符和析构函数对引用计数进行判断是否为0,如果是,则撤销对象。
通用的句柄类模板实现如下(摘自《C++ Primer》):
1 template<class T> class Handle 2 { 3 public: 4 Handle(T *p = 0):ptr(p), use(new size_t(1)){} 5 T& operator*(); 6 T* operator->(); 7 8 const T& operator*()const; 9 const T* operator->()const; 10 11 Handle(const Handle& h):ptr(h.ptr), use(h.use) 12 { ++*use; } 13 14 Handle& operator=(const Handle&); 15 ~Handle() { rem_ref(); } 16 17 private: 18 T* ptr; //shared object 19 size_t *use; //count of how many Handles spoint to *ptr 20 void rem_ref() 21 { 22 if(--*use == 0) 23 { 24 delete ptr; 25 delete use; 26 } 27 } 28 }; 29 30 template<class T> 31 inline Handle<T>& Handle<T>::operator=(const Handle &rhs) 32 { 33 ++*rhs.use; //protect against self-assignment 34 rem_ref(); //decrement use count and delete pointers if needed 35 ptr = rhs.ptr; 36 use = rhs.use; 37 38 return *this; 39 } 40 41 template<class T> 42 inline T& Handle<T>::operator*() 43 { 44 if(ptr) return *ptr; 45 throw std::runtime_error 46 ("dereference of unbound Handle"); 47 } 48 49 template<class T> 50 inline T* Handle<T>::operator->() 51 { 52 if(ptr) return ptr; 53 throw std::runtime_error 54 ("access through of unbound Handle"); 55 } 56 57 template<class T> 58 inline const T& Handle<T>::operator*()const 59 { 60 if(ptr) return *ptr; 61 throw std::runtime_error 62 ("dereference of unbound Handle"); 63 } 64 65 template<class T> 66 inline const T* Handle<T>::operator->()const 67 { 68 if(ptr) return ptr; 69 throw std::runtime_error 70 ("access through of unbound Handle"); 71 }
使用句柄类的例子:
1 int main() 2 { 3 Handle<int> hp(new int(42)); 4 { 5 //new scope 6 Handle<int> hp2 = hp; 7 cout<<*hp<<" "<<*hp2<<endl; 8 *hp2 = 10; 9 } 10 cout<<*hp<<endl; 11 return 0; 12 }
代码line6使用hp构造了新的Handle对象hp2,二者指向同一块内存。line8对*hp2重新赋值,此时二者指向内存值由42变为10,退出new scope后,hp2对象析构,但引用计数值未减小到0,line10输出*hp的值,由于二者指向内存空间相同,所以此处输出10,之前的42被覆盖,退出main函数时,引用计数减小到0,此时hp析构函数将构造时分配的空间delete。
可以看出,上面展示的句柄类可以完成动态分配指针的托管,并且高效完成复制与赋值操作。但是在改变*hp2时,hp托管的对象值同样改变了,这可能会违背开发人员的意愿,毕竟hp和hp2是两个对象。在更改hp2时,要做到防止hp托管对象被改变,那么句柄类需要自动复制对象并与原对象断开,这被称为写时复制(copy-on-write)。
未完待续……