当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。
智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
实现引用计数有两种经典策略:一是引入辅助类,二是使用句柄类。下面分别介绍这些内容。
class TestPtr { public: TestPtr(int *p): ptr(p) { } ~TestPtr( ) { delete ptr; } // other operations private: int *ptr; // other data };
在这种情况下,类TestPtr对象的任何拷贝、赋值操作都会使多个TestPtr对象共享相同的指针。但在一个对象发生析构时,指针指向的对象将被释放,从而可能引起悬垂指针。
现在我们使用引用计数来解决这个问题,一个新的问题是引用计数放在哪里。显然,不能放在TestPtr类中,因为多个对象共享指针时无法同步更新引用计数(类的每个对象维护着自己的引用计数)。
方案一:引入辅助类
这里给出的解决方案是,定义一个单独的具体类(RefPtr)来封装指针和相应的引用计数。由于这个类只是用于对类TestPtr中的成员指针ptr进行了封装,无其它用途,所以把引用计数类RefPtr的所有成员均定义为private(class和struct不同在于,class成员访问权限默认是private,实现封装,struct是public),并把类TestPtr声明为它的友元类,使TestPtr类可以访问RefPtr类。示例代码如下:
class RefPtr { friend class TestPtr; int *ptr; size_t count; RefPtr (int *p): ptr(p), count(1) {} ~RefPtr () { delete ptr; } }; class TestPtr { public: TestPtr(int *p): ptr(new RefPtr(p)) { } TestPtr(const TestPtr& src): ptr(src.ptr) { //copying函数
++ptr->count; //增加引用计数
} TestPtr& operator= (const TestPtr& rhs) { //copying函数
// self-assigning is also right ++rhs.ptr->count; if (--ptr->count == 0) delete ptr; ptr = rhs.ptr; return *this; } ~TestPtr() { if (--ptr->count == 0) delete ptr; } private: RefPtr *ptr; };
当希望每个TestPtr对象中的指针所指向的内容改变而不影响其它对象的指针所指向的内容时,可以在发生修改时,创建新的对象,并修改相应的引用计数。这种技术的一个实例就是写时拷贝(Copy-On-Write)。
这种方案的缺点是每个含有指针的类(TestPtr )的实现代码中都要自己控制引用计数,比较繁琐。特别是当有多个这类指针时,维护引用计数比较困难。
方案二:使用句柄类(handler class)
为了避免上面方案中每个使用指针的类自己去控制引用计数,可以用一个类把指针封装起来。封装好后,这个类对象可以出现在用户类使用指针的任何地方,表现为一个指针的行为(智能指针重载->操作符,直接返回对象的引用,用以操作对象)。我们可以像指针一样使用它,而不用担心普通成员指针所带来的问题,我们把这样的类叫句柄类。在封装句柄类时,需要申请一个动态分配的引用计数空间,指针与引用计数分开存储。实现示例如下:
#include <iostream> #include <stdexcept> using namespace std; #define TEST_SMARTPTR class Stub { public: Stub(){ cout<<"Stub: Constructor"<<endl; } ~Stub(){ cout<<"Stub: Destructor"<<endl; } void print() { cout<<"Stub: print"<<endl; } }; template <typename T> class SmartPtr { public: SmartPtr(T *p = 0): ptr(p), pUse(new size_t(1)) { cout<<"SmartPtr: Constructor"<<endl; } SmartPtr(const SmartPtr& src): ptr(src.ptr), pUse(src.pUse) { // copying函数,使用被复制智能对象的ptr和pUse初始化新对象 cout<<"SmartPtr: Copying Constructor"<<endl; getUse(); ++*pUse; // 对上面初始化完成之后的pUse引用计数加1 // 这里pUse为一个指针,copy构造之后,被复制对象以及复制对象各自维护的pUse指针所指的内存块相同,当上面“++*pUse”对其所指内容加1之后,两个指针返回的值也都相应加1 } //原始对象在智能指针的包裹下成为智能对象,对智能对象的赋值操作导致智能对象维护的引用计数增加 SmartPtr& operator= (const SmartPtr& rhs) { // copying函数 // self-assigning is also right cout<<"SmartPtr: Copying Assignment"<<endl; ++*rhs.pUse; // 获取将被赋值(右操作数)的智能对象的引用计数,并加1 decrUse(); // 清除左智能对象的引用计数和原始对象指针 ptr = rhs.ptr; // 左智能对象原始对象指针 指向 被赋值的智能对象的原始对象 pUse = rhs.pUse; //左智能对象保存被赋值的智能对象的引用计数 return *this; // 返回左智能对象 } // 智能指针重载->操作符,直接返回对象的引用,用以操作对象 T *operator->() { if (ptr) return ptr; throw std::runtime_error("access through NULL pointer"); } const T *operator->() const { if (ptr) return ptr; throw std::runtime_error("access through NULL pointer"); } T &operator*() { if (ptr) return *ptr; throw std::runtime_error("dereference of NULL pointer"); } const T &operator*() const { if (ptr) return *ptr; throw std::runtime_error("dereference of NULL pointer"); } ~SmartPtr() { decrUse(); #ifdef TEST_SMARTPTR std::cout<<"SmartPtr: Destructor"<<std::endl; // for testing #endif } size_t getUse() { std::cout<<"SmartPtr: getUse *pUse = "<< *pUse << std::endl; } private: void decrUse() { std::cout<<"SmartPtr: decrUse *pUse = "<< *pUse << std::endl; if (--*pUse == 0) { std::cout<<"SmartPtr: decrUse"<<std::endl; delete ptr; delete pUse; } } T *ptr; size_t *pUse; }; int main() { try { // 智能指针内的原始指针为空 SmartPtr<Stub> t; // SmartPtr: Constructor, SmartPtr: Destructor t->print(); } catch (const exception& err) { cout<<err.what()<<endl; //access through NULL pointer } std::cout<<"1 --------------------------------------------"<<std::endl; SmartPtr<Stub> t1(new Stub); //Stub: Constructor,SmartPtr: Constructor t1.getUse(); std::cout<<"2 --------------------------------------------"<<std::endl; SmartPtr<Stub> t2(t1); t1.getUse(); t2.getUse(); std::cout<<"3 --------------------------------------------"<<std::endl; SmartPtr<Stub> t3(new Stub); t1.getUse(); t2.getUse(); t3.getUse(); std::cout<<"4 --------------------------------------------"<<std::endl; t3 = t2; t1.getUse(); t2.getUse(); t3.getUse(); t1->print(); (*t3).print(); std::cout<<"--------------------------------------------"<<std::endl; return 0; }
输出:
SmartPtr: Constructor SmartPtr: decrUse *pUse = 1 SmartPtr: decrUse SmartPtr: Destructor access through NULL pointer 1 -------------------------------------------- Stub: Constructor SmartPtr: Constructor SmartPtr: getUse *pUse = 1 2 -------------------------------------------- SmartPtr: Copying Constructor SmartPtr: getUse *pUse = 1 SmartPtr: getUse *pUse = 2 SmartPtr: getUse *pUse = 2 3 -------------------------------------------- Stub: Constructor SmartPtr: Constructor SmartPtr: getUse *pUse = 2 SmartPtr: getUse *pUse = 2 SmartPtr: getUse *pUse = 1 4 -------------------------------------------- SmartPtr: Copying Assignment SmartPtr: decrUse *pUse = 1 SmartPtr: decrUse Stub: Destructor SmartPtr: getUse *pUse = 3 SmartPtr: getUse *pUse = 3 SmartPtr: getUse *pUse = 3 Stub: print Stub: print -------------------------------------------- SmartPtr: decrUse *pUse = 3 SmartPtr: Destructor SmartPtr: decrUse *pUse = 2 SmartPtr: Destructor SmartPtr: decrUse *pUse = 1 SmartPtr: decrUse Stub: Destructor SmartPtr: Destructor [Finished in 1.2s]