使能 enable_shared_from_this 和 make_shared 提供相同的优化吗?

13

据我理解,make_shared<T>(...) 可能提供一些内存分配优化(它可以在与类 T 的实例相同的内存块中分配引用计数器)。

enable_shared_from_this 是否提供相同的优化呢?因此:

class T : std::enable_shared_from_this<T> {};
...
auto t = std::shared_ptr<T>(new T);

相当于:

class T {};
...
auto t = std::make_shared<T>();

如果不考虑sizeof(T)。


5
两者完全正交。当你已经有一个共享指针时,enable_shared_from_this 才会相关。 - Kerrek SB
@KerrekSB 我认为 enable_shared_from_this 添加了一些数据到类中,这些数据可以被 shared pointer 实现所使用。对我来说,最简单的方法是将引用数量作为 enable_shared_from_this 的一部分,并且这可能有助于在第一种情况下减少内存分配。 - Dmitry Poroh
我很想说“是的”,否则就没有办法从对象内部检索refcount块。但这只是一种猜测。 - Quentin
1
我不明白混淆的原因:你可以使用 p = make_shared<T>() 或者 p = shared_ptr<T>(new T()) 来创建一个共享指针。无论哪种方式,你都可以使用 p->shared_from_this() 来获取另一个共享指针。 - Kerrek SB
2
@KerrekSB 这个问题不是关于如何检索共享指针的,而是关于内存分配优化的问题。 - Dmitry Poroh
显示剩余2条评论
1个回答

11

enable_shared_from_this提供同样的优化吗?

不是的。从标准中的措辞可以看出,enable_shared_from_this<T>有一个weak_ptr<T>数据成员。这为类添加了一个指向包含引用计数的控制块的weak_ptr<T>,该控制块并未直接包含引用计数。包含引用计数的控制块仍然存在于对象外部。

包含引用计数的控制块必须在对象之后才能被销毁,这样曾经引用该对象的其他weak_ptr对象仍然可以访问控制块,以检查其是否已过期。

如果控制块在对象内部,则当对象被销毁时,它也会被销毁,而悬挂的weak_ptr无法安全地确定对象是否已过期。理论上,即使所属的对象已被销毁,控制块的内存仍可能保持分配状态,并继续使用和更新引用计数,但这似乎非常丑陋(而且意味着对象将不会随delete一起被销毁,需要显式调用析构函数和显式operator delete以释放内存)。

如果拥有的shared_ptr使用自定义删除器或自定义分配器创建,则也无法使用嵌入式控制块,因为这些对象的大小事先是不知道的。在这种情况下,仍需要分配一个外部控制块,除了已嵌入于enable_shared_from_this<T>基类中的控制块之外,更浪费空间。


感谢您的完美解释! - Dmitry Poroh
这是一个非常有趣的问题,但我越想它如何工作,就越发现了更多的复杂性。 - Jonathan Wakely
我认为,在使用 make_shared 的情况下,只有在最后一个 weak_ptr 指向控制块时,实例的内存才能被释放。但与 enable_shared_from_this 相比,它可以从 C++ 语言角度清晰地实现(不使用已销毁的对象)。 - Dmitry Poroh
如果考虑到重载的new/delete,问题会变得更加复杂 :) - Dmitry Poroh
2
是的,使用make_shared,直到最后一个弱引用被释放,内存才会被释放。但这没关系,因为make_shared控制了分配+构造和销毁+释放内存,所以可以正确地完成所有操作。使用shared_ptr<X>(new X)创建的对象应该使用delete进行销毁,但这将阻止控制块超越对象(因为内存将在析构函数运行时立即释放)。 - Jonathan Wakely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接