必须将enable_shared_from_this作为第一个基类吗?

8

我的类从多个基类继承,其中之一是std::enable_shared_from_this。它必须是第一个基类吗?

假设下面的示例代码:

struct A { ~A(); };
struct B { ~B(); };
struct C : A, B, std::enable_shared_from_this<C> {};

std::make_shared<C>(); 

~A()~B() 运行时,我能确信存储 C 所在的空间仍然存在吗?

1
@S.M. 这不是一个访问问题。我知道 enable_shared_from_this 必须是一个可访问的明确的基类。在我的例子中,它是的。C 是一个结构体。它公开继承。 - Filipp
1
是的,但基础访问权限取决于继承的对象,而不是被继承的对象。如果您想要,我可以更改我的示例。实际代码使用“class”和“public”。我选择使用“struct”作为示例以避免输入。 - Filipp
4
标准库中的文档如下所述:“**[util.smartptr.weak.dest]** ~weak_ptr(); 作用: 销毁此 weak_ptr 对象,但不会影响存储指针所指向的对象。” 强调是我的。 - Igor Tandetnik
1
@Filipp 当最后一个shared_ptr消失时,存储对象的生命周期也随之结束。即使weak_ptr保留了控制块以防止其被释放,我认为这并不重要。 - HolyBlackCat
1
@HolyBlackCat 这个问题是“_当 ~A() 和 ~B() 运行时,_(...)”,所以显然我们知道 C 的生命周期已经结束,因为最后一个共享的 shared_ptr<T> 已经被销毁(或重置)。问题在于当在 T 对象销毁开始时仍有剩余的 weak_ptr<T> 时如何处理资源。 - curiousguy
显示剩余5条评论
3个回答

5
当 ~A() 和 ~B() 运行时,我不能确定 C 对象所在的内存是否仍然存在。不管基类的顺序是什么,甚至使用(或不使用)enable_shared_from_this都是无关紧要的。当一个 C 对象被销毁(无论如何),~C() 将在 ~A() 和 ~B() 之前调用,因为这是基类析构函数工作的方式。如果您尝试在任何一个基类析构函数中“重建” C 对象并访问其中的字段,则这些字段将已经被销毁,因此您将得到未定义的行为。

不回答我的问题。我从未提到过尝试“重构”任何C。答案应该是:“enable_shared_from_this可以出现在基类列表的任何位置,实现需要在整个对象销毁后释放内存,无论它如何继承自enable_shared_from_this”,或者“它必须是第一个基类,在其他任何地方继承都是UB”,或者“此行为未指定或实现质量不佳”。 - Filipp
@Filipp:答案是一个组合——它们可以出现在任何地方,而且实现可以在对象的一部分被销毁之后(以及基类被销毁之前)释放该对象的一部分内存。没有任何要求只能在整个对象被销毁之后才能释放内存。 - Chris Dodd

2
当运行~A()~B()时,我可以确定C所在的存储空间仍然存在吗?
当然可以!使用试图释放自身内存(所在的内存)的基类将会很难。我甚至不确定它是否合法。
实现不会这样做:当一个shared_ptr<T>被销毁或重置时,共享所有权的T的引用计数(RC)会递减(原子性);如果在递减中达到0,则开始销毁/删除T
然后,弱拥有者或T存在计数会递减(原子性),因为T不再存在:我们需要知道是否还有其他对控制块感兴趣的实体;如果递减结果非零,则表示某些weak_ptr存在,它们共享(可以是1份或100%)控制块的所有权,并且现在负责释放内存。
无论哪种方式,原子递减最终都会得到最后一个共同所有者的值为零。
这里没有线程,没有不确定性,显然,在销毁C期间,最后一个weak_ptr<T>也已经被销毁。(您问题中未写明的假设是没有其他weak_ptr<T>被保留。)
销毁始终按照确切的顺序进行。控制块用于销毁,因为没有shared_ptr<T>知道(通常情况下)调用哪个(潜在的非虚拟)最终派生类的析构函数。(控制块还知道不要在make_shared的共享计数达到零时释放内存。)
各种实现之间唯一的实际变化似乎在于内存栅和避免某些常见情况下的原子操作的细节。

这就是我一直在寻找的答案!谢谢!关键在于对象的存在实际上算作一个隐式的弱引用。也就是说,即使没有 weak_ptr,使用 make_shared 创建出来的对象的 weak_count 为1。释放 shared_ptr 会先将 use_count 减1。如果变成了0,则销毁对象(但不会销毁控制块)。然后 weak_count 减1,如果变成了0,则销毁并释放控制块。继承自 enable_shared_from_this 的对象的 weak_count 起始值为2。这正是 STL 实现者所期望的卓越解决方案。 - Filipp
只是一个吹毛求疵的小问题:STL 是标准模板库,除了历史文物(HP STL 或 SGI STL)之外,它只是非正式定义的;它涉及遵循容器、迭代器和“算法”要求的类型。STL 并不严格限于模板,因为它使用了一些非模板类(例如 random_access_iterator_tag)。有非正式协议称与容器相关的任何内容都是 STL 的一部分。简而言之:标准库中并非所有模板都属于 STL,也并非所有非模板都在其范围之外。 - curiousguy

-1
如果您创建一个类型为C的对象c,并通过从基类enable_shared_from_this继承来获得基类A、B和引用计数器,首先为整个结果对象(包括一般的基类和基类enable_shared_from_this)分配内存。只有在最后一个所有者(即shared_ptr)放弃所有权时,对象才会被销毁。在那时,将在~C之后运行~enable_shared...、~B和~A。完整分配的内存仍然保证存在,直到~A析构函数运行完成之后。在~A运行之后,整个对象内存将被一次性释放。
所以回答您的问题:
当~A()和~B()运行时,我可以确定C所在的存储是否仍然存在吗?
是的,尽管您无法合法地访问它,但是为什么需要知道呢?您试图避免哪个问题?

你所写的是正确的,但并没有回答我的问题。当然,基类析构函数会在派生类之后运行。我想知道的是,即使enable_shared_from_this不是第一个基类,shared_ptrweak_ptrenable_shared_from_this的实现是否需要保留足够长的时间来确保这是安全的。 - Filipp
啊,好的。看了你最初的问题:并没有明确的“这个”[就像你上面评论中的“让这个保存”]你想要实现什么。我会编辑我的答案以反映我此时理解的问题。 - Andreas_75
在另一个基类之后继承自 enable_shared_from_this,使其更安全。 - Filipp

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