EMCPP条款19:使用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::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_ptr
或std::auto_ptr
指针)出发构造一个std::shared_ptr
时,会创建一个控制块。专属所有权指针不使用控制块,因此不应该存在所指涉到的对象来说不应存在控制块(作为构造过程的一部分,std::shared_ptr
被指定了其所指涉到的对象的所有权,因此那个专属所有权的智能指针会被置空)。 - 当
std::shared_ptr
构造函数使用裸指针作为实参来调用时,它会创建一个控制块。如果想从一个已经拥有控制块的对象出发来创建一个std::shared_ptr
,你大概会传递一个std::shared_ptr
或std::weak_ptr
(参见条款20)而非裸指针作为构造函数的实参。如果给std::shared_ptr
的构造函数传递std::shared_ptr
或std::weak_ ptr
作为实参,则不会创建新的控制块,因为它们可以依赖传入的智能指针以指涉到任意所需的控制块。
要点速记
- std::shared_ptr 提供方便的手段,实现了任意资源在共享所有权语义下进行生命周期管理的垃圾回收。
- 与
std::unique_ptr
相比,std::shared_ptr
的尺寸通常是裸指针尺寸的两倍,它还会带来控制块的开销,并要求原子化的引用计数操作。 - 默认的资源析构通过
delete
运算符进行,但同时也支持定制删除器。删除器的型别对std::shared_ptr
的型别没有影响 。 - 避免使用裸指针型别的变量来创建
std::shared_ptr
指针。