EMCPP条款19:使用std::shared_ptr管理具备共享所有权的资源

YiQi 管理员

通过 std::shared_ptr 这种智能指针访问的对象采用共享所有权来管理其生存期。没有哪个特定的 std::shared_ptr 拥有该对象。取而代之的是,所有指涉到它的 std::shared_ptr 共同协作,确保在不再需要该对象的时刻将其析构。当最后一个指涉到某对象的 std::shared_ptr 不再指涉到它时(例如,由于该 std::shared_ptr 被析构,或使其指涉到另一个不同的对象),该 std::shared_ptr 会析构其指涉到的对象。正如垃圾回收一样,用户无须操心如何管理被指涉到对象的生存期,但又如析构函数一样,该对象的析构函数的时序是确定的。

引用计数的存在会带来一些性能影响:

  • std::shared_ptr 的尺寸是裸指针的两倍
  • 引用计数的内存必须动态分配
  • 引用计数的递增和递减必须是原子操作

从一个已有 std::shared_ptr 移动构造一个新的 std::shared_ptr 会将源 std::shared_ptr 置空,这意味着一旦新的 std::shared_ptr 产生后,原有的 std::shared_ptr 将不再指涉到其资源,结果是不需要进行任何引用计数操作。

std::unique_ptr 类似(参见条款 18), std::shared_ptr也使用delete` 运算符作为其默认资源析构机制,但它同样支持自定义析构器。

感兴趣的话可以去查看这部分原文

我们可以想象与 std::shared_ptr<T> 对象相关的内存,如图所示。

控制块的创建遵循了以下规则:

  • std::make_shared (参见条款21)总是创建一个控制块。std::make_shared 会生产出一个用以指涉到的新对象,因此在调用 std::make_shared 的时刻,显然不会有针对该对象的控制块存在。
  • 从具备专属所有权的指针(即 std::unique_ptrstd::auto_ptr 指针)出发构造一个 std::shared_ptr 时,会创建一个控制块。专属所有权指针不使用控制块,因此不应该存在所指涉到的对象来说不应存在控制块(作为构造过程的一部分,std::shared_ptr 被指定了其所指涉到的对象的所有权,因此那个专属所有权的智能指针会被置空)。
  • std::shared_ptr 构造函数使用裸指针作为实参来调用时,它会创建一个控制块。如果想从一个已经拥有控制块的对象出发来创建一个 std::shared_ptr,你大概会传递一个 std::shared_ptrstd::weak_ptr(参见条款20)而非裸指针作为构造函数的实参。如果给 std::shared_ptr 的构造函数传递 std::shared_ptrstd::weak_ ptr 作为实参,则不会创建新的控制块,因为它们可以依赖传入的智能指针以指涉到任意所需的控制块。

要点速记

  • std::shared_ptr 提供方便的手段,实现了任意资源在共享所有权语义下进行生命周期管理的垃圾回收。
  • std::unique_ptr 相比,std::shared_ptr 的尺寸通常是裸指针尺寸的两倍,它还会带来控制块的开销,并要求原子化的引用计数操作。
  • 默认的资源析构通过 delete 运算符进行,但同时也支持定制删除器。删除器的型别对 std::shared_ptr 的型别没有影响 。
  • 避免使用裸指针型别的变量来创建 std::shared_ptr 指针。
此页目录
EMCPP条款19:使用std::shared_ptr管理具备共享所有权的资源