C++ std::enable_shared_from_this() 函数

2025年3月22日 | 7 分钟阅读

std::enable_shared_from_this() 函数是 C++ 中的一个实用工具函数,它允许一个对象创建指向自身(它所拥有的对象)的 `std::shared_ptr` 实例。当一个类被 `shared_ptr` 包装时,它可以在类内部安全地获取 `shared_ptr` 引用。

多个 shared_ptr 指向同一对象

当一个 std::shared_ptr 管理一个 对象 时,它带有一个引用计数机制。这个引用计数会显示有多少个 `shared_ptr` 对象指向某个特定对象。当引用计数达到零时,在垃圾回收的上下文中,对象会被销毁,这个过程会释放对象所在的内存空间。

现在,假设您有一个已经被 `std::make_shared` 管理的对象(即,对象是通过 `std::make_shared` 创建或传递的)。如果您尝试在对象本身内部通过 `std::enable_shared_from_this` 创建一个新的 `shared_ptr`,使用 `shared_from_this()`,那么当一个 `shared_ptr<T>` 调用 `this` 时,会发生以下情况:

  • 新的 shared_ptr:现在,您创建了一个新的 `shared_ptr` 对象,它有自己新的引用计数。
  • 双重引用计数:现在,有两个 `shared_ptr` 对象指向同一个对象,但它们都有各自的引用计数。这意味着,如果一个 `shared_ptr` 类认为该对象仍然可用,而另一个 `shared_ptr` 类认为它自己是唯一管理该对象的,那么它可能会将其删除。这会导致未定义的行为,并可能导致各种问题。
  • 双重删除:当两个 `shared_ptr` 实例都引用该对象,并且它们彼此独立时,当它们各自的引用计数都降为零并试图删除该对象时,就会发生双重删除错误。
  • 悬空指针:原始的 `shared_ptr` 可能认为它仍然拥有一个有效的对象。然而,如果新的 `shared_ptr` 先销毁了该对象,原始的 `shared_ptr` 将持有一个悬空指针,这可能导致运行时崩溃或其他未定义的行为。

使用 std::enable_shared_from_this 方法

std::enable_shared_from_this() 函数旨在解决这个问题,它使得一个已经被 `shared_ptr` 所拥有的对象,能够获取指向自身的另一个 `shared_ptr`,而不会形成新的引用计数。

当对象首次由 `shared_ptr` 管理时(例如,通过 `std::make_shared`),对象会存储指向包含引用计数器的控制块的指针。当您在对象内部使用 `shared_from_this()` 时,它会利用这个已有的控制块,而不是创建一个新的。如果您尝试在对象创建后的第一次 `shared_ptr` 之前使用 `shared_from_this()`,或者当您使用 `shared_ptr(T* ptr)` 构造函数(而不是 `make_shared(ptr)`)来创建 `shared_ptr` 时,此功能将不起作用,因为此时没有与对象关联的控制块。

当您使用 `std::enable_shared_from_this` 时,该类存储指向控制块(管理 `shared_ptr` 的引用计数)的弱指针,而不是直接指向对象。当为该对象创建第一个 `shared_ptr` 时,会自动创建此弱指针,所有这些都在后台处理。

使用弱指针的原因是为了避免增加引用计数,同时仍然能够跟踪控制块。这使得对象能够安全地在之后创建指向自己的新的 `shared_ptr` 实例,而不会导致双重引用计数或双重删除等问题。

正如您所知,当调用 `shared_from_this()` 时,它会检查此弱引用。如果弱引用仍然有效(这意味着对象尚未被销毁),它将创建一个新的 `shared_ptr`,该 `shared_ptr` 共享原始 `shared_ptr` 对象指向的对象。新的 `shared_ptr` 不会创建新的引用计数,而是使该对象的引用计数与原始类型 `shared_ptr` 的对象保持一致。

这有助于避免双重删除,只要至少存在一个 `shared_ptr`,对象就保持有效。

示例

让我们用一个例子来说明 C++ 中 std::enable_shared_from_this() 函数的用法。

输出

 
=== Testing MyDerived class ===
MyBase constructor: DerivedObject
MyDerived constructor: DerivedObject
MyDerived object: DerivedObject, value: 42
derivedPtr use_count(): 2
derivedPtr2 use_count(): 2
Inside manipulateObject()
MyDerived object: DerivedObject, value: 42
Another pointer use_count(): 4
After manipulateObject() use_count(): 2
MyDerived destructor: DerivedObject
MyBase destructor: DerivedObject

=== Storing objects in vector ===
MyBase constructor: BaseObject1
MyBase constructor: DerivedObject1
MyDerived constructor: DerivedObject1
MyBase constructor: DerivedObject2
MyDerived constructor: DerivedObject2
MyBase object: BaseObject1
MyDerived object: DerivedObject1, value: 10
MyDerived object: DerivedObject2, value: 20
=== Complex interaction ===
MyBase constructor: ComplexBaseObject
MyBase constructor: ComplexDerivedObject
MyDerived constructor: ComplexDerivedObject
Inside manipulateObject()
MyBase object: ComplexBaseObject
Another pointer use_count(): 2
Inside manipulateObject()
MyDerived object: ComplexDerivedObject, value: 100
Another pointer use_count(): 3
basePtr use_count(): 1
derivedPtr use_count(): 2
baseFromDerived use_count(): 2
MyDerived destructor: ComplexDerivedObject
MyBase destructor: ComplexDerivedObject
MyBase destructor: ComplexBaseObject
MyBase destructor: BaseObject1
MyDerived destructor: DerivedObject1
MyBase destructor: DerivedObject1
MyDerived destructor: DerivedObject2
MyBase destructor: DerivedObject2   

说明

该代码演示了在 层次结构中使用 `std::enable_shared_from_this()` 函数通过 `std::shared_ptr` 安全地管理对象所有权。基类 `MyBase` 继承自 `std::enable_shared_from_this<MyBase>`,这允许它使用 `shared_from_this()` 创建自身的 `shared_ptr` 实例。派生类 `MyDerived` 扩展了 `MyBase`,并同样利用 `shared_from_this()` 返回转换为其类型的 `shared_ptr` 实例。

`getPtr()` 和 `getDerivedPtr()` 等关键函数可确保对象能够安全地返回 `shared_ptr` 实例,而不会创建单独的引用计数。`manipulateObject()` 函数展示了在回调场景中如何使用 `shared_from_this()` 来维护正确的引用计数。`testDerived()` 函数测试了在派生类中安全使用 `shared_from_this()`,而 `storeObjectsInVector()` 则管理向量中的多个对象。

最后,`complexInteraction()` 函数探讨了基类和派生类之间的多态行为,确保了对象所有权和销毁的正确性。该代码强调了 `shared_from_this()` 如何通过确保所有 `shared_ptr` 实例共享相同的引用计数来防止双重删除等问题,从而保证了安全的对象生命周期管理。

复杂度分析

时间复杂度

  • 对象创建(std::make_shared):对于每个对象,时间复杂度为 O(1),因为内存分配和引用计数初始化每个对象执行一次。
  • 引用计数:对于每个指针操作,`shared_ptr` 的引用计数增减的时间复杂度为 O(1)。
  • Shared_from_this():此函数从现有对象检索 `shared_ptr` 并向控制块添加新引用,时间复杂度为 O(1)。
  • 向量操作(storeObjectsInVector):将对象插入 `std::vector` 的平均时间复杂度为 O(1),除非发生重新分配,此时时间复杂度为 O(n),其中 n 是向量的当前大小。

空间复杂度

  • 对象内存:每个对象(基类或派生类)本身的空间复杂度为 O(1),`shared_ptr` 管理的控制块的空间复杂度为 O(1)。
  • 向量存储:向量 中存储 n 个对象,对于对象 指针 和相关的引用计数控制块,空间复杂度为 O(n)。