首页 > 代码库 > 引用计数指针

引用计数指针

如前面内存泄露中所讲的,引用计数指针可以被复制。因此,一个智能指针的几份拷贝可以指向同一个对象。这就产生了由哪份拷贝负责删除它们共同指向的对象这个问题。答案是这组智能指针中最后消亡的那个将删除它所指向的对象。这类似于家居规则:“最后一个离开屋子的人负责关灯。”为了实现这个算法,这些指针共享一个计数器,记录有多少个智能指针引用同一个对象,即“引用计数”这个术语的由来。引用计数的应用范围很广:这个术语简单意味着,编译器具有一个隐藏的整型变量作为计数器。每当有人创建了一个指向目标对象的智能指针的一份拷贝时,编译器就会增加这个计数器的值。当任何智能指针被删除时,编译器就会减少计数器的值。因此目标对象只要有需要就会存在,当所有指向它的智能指针都不存在时就会被销毁。

以下是引用计数指针的一种实现:

template <typename T>
class RefCountPtr
{
public:

	explicit RefCountPtr(T* p = NULL)
	{
		Create(p);
	}

	RefCountPtr(const RefCountPtr<T>& rhs)
	{
		Copy(rhs);
	}

	RefCountPtr<T>& operator=(const RefCountPtr<T>& rhs)
	{
		if(ptr_ != rhs.ptr_)
		{
			Kill();
			Copy(rhs);
		}
		return *this;
	}

	RefCountPtr<T>& operator=(T* p)
	{
		if(ptr_ != p)
		{
			Kill();
			Create(p);
		}
		return *this;
	}

	~RefCountPtr()
	{
		Kill();
	}

	T* Get()const 
	{
		return ptr_;
	}

	T*operator->() const
	{
		SCPP_TEST_ASSERT(ptr_ != NULL,
			"Attempt to use operator -> on MULL pointer.");
		return ptr_;
	}

	T& operator* ()const
	{
		SCPP_TEST_ASSERT(ptr_ != NULL,
			"Attempt to use operator * on NULL pointer.");
		return *ptr_;
	}
};

注意,这个类同时提供了拷贝构造函数和赋值操作符,因此可以复制这些指针。在这种情况下,原来的指针和复制产生的指针指向同一个对象(如果原来的指针为NULL,则复制产生的指针也为NULL)。在这层意义上,它们的行为与常规的“原始”T*指针相同。如果不再需要使用这个对象,可以通过把引用计数指针赋值为NULL将其“杀死”。

引用计数指针存在一些问题。首先,创建一个非NULL的实参具有比较大的开销,因为编译器使用new操作符在堆上分配一个整数,这是一种相对较为缓慢的操作。其次,引用计数指针当然并不是多线程安全的。虽然多线程超出了本书的范围,但还是值得注意。如果我们确信需要复制指针,并且可以合理地确认创建一个引用计数指针所产生的开销相对于剩余代码的执行速度可以忽略不计时,就可以使用引用计数指针。