C++标准库中的 shared_ptr 是引用计数型指针,记录有多少用户共享它所指向的对象。当没有用户使用对象时,shared_ptr 类负责释放资源。为了说明引用计数如何工作,我将结合《C++ Primer(第5版)》第 13 章的内容,设计自己的引用计数。

引用计数的工作方式

  1. 除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录有多少对象与正在创建的对象共享状态。当我们创建一个对象时,只有一个对象共享状态,因此将计数器初始化为 1。
  2. 拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定对象的状态又被一个新用户所共享。
  3. 析构函数递减计数器,指出共享状态的用户少了一个。如果计数器变为 0,则析构函数释放状态。
  4. 拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧对象的计数器变为 0,意味着它的共享状态没有用户了,拷贝赋值运算符就必须销毁状态。

引用计数器的实现

如果引用计数器直接作为类的成员的,例如我们有一个类 HasPtr,管理一个 string 类型的指针:

HasPtr p1("Hiya!");
HasPtr p2(p1); // p1 和 p2 指向相同的 string
HasPtr p3(p1); // p1、p2 和 p3 都指向相同的 string

问题:创建 p2 的时候可以递增 p1 中的计数器并拷贝到 p2,同样地方式创建 p3,但是这是似乎无法再更新 p2 中的引用计数了。
解决:一种方法是将计数器保存在动态内存中。当创建一个对象时,我们也分配一个新的计数器。当拷贝或赋值对象时,我们拷贝指向计数器的指针。使用这种方法,副本和原对象都会指向相同的计数器。

定义使用引用计数的类

下面是一个使用引用计数的类的简单实现:

class HasPtr {
public:
HasPtr(const string &s = string()) : ps(new string(s)), use(new size_t(1)) { }
HasPtr(const HasPtr &rhs) : ps(rhs.ps), use(rhs.use) { ++*use; }
HasPtr &operator=(const HasPtr &rhs) {
++*rhs.use;
if (--*use == 0) {
delete ps;
delete use;
}
ps = rhs.ps;
use = rhs.use;
return *this;
}
~HasPtr() {
if (--*use == 0) {
delete ps;
delete use;
}
}
}