在同一个对象上创建多个 shared_ptr "families" 时 shared_from_this 的意外行为

Unexpected behavior from shared_from_this when creating multiple shared_ptr "families" over same object

下面是一些示例代码(在线here):

#include <memory>

struct Foo : public std::enable_shared_from_this<Foo> {};

void example()
{
    auto sharedFoo = std::make_shared<Foo>();
    std::shared_ptr<Foo> nonDeletingSharedFoo(sharedFoo.get(), [](void*){});
    nonDeletingSharedFoo.reset();
    sharedFoo->shared_from_this(); // throws std::bad_weak_ptr
}

我看到的(在多个编译器下)是当 nonDeletingSharedFoo 被重置时, enable_shared_from_this 内部使用的 weak_ptr 过期,所以后续调用 shared_from_this失败。

我希望 nonDeletingSharedFoo 有一个完全独立于 sharedFoo 的引用计数,因为它是从原始指针构造的,但显然它仍然影响 Foo 的弱计数对象的内部 weak_ptr。我认为这是因为 shared_ptr 构造函数 and/or 析构函数在指向的类型实现 enable_shared_from_this.

时做了一些特殊的事情

那么这段代码是否违反了标准?有没有解决方案,或者只是不可能在实现 enable_shared_from_this 的对象上有多个 shared_ptr "families"?

当您使用从 sharedFoo (sharedFoo.get()) 获取时,您会得到包含在shared_ptr。所以 nonDeletingSharedFoo 无法访问 shared_ptr 并且当您重置 nonDeletingSharedFoo 时,您释放了地址内存。所以 sharedFoo 的对象现在不存在了。

你在这里处于灰色地带:enable_shared_from_this 通常是通过让 shared_ptr 构造函数实现的,这些构造函数拥有指向从 enable_shared_from_this 派生的对象的原始指针的所有权设置 weak_ptr包含在对象里面。因此,稍后对 shared_from_this() 的调用对 return 有所帮助。当您 "reparent" sharedFoo 原始 weak_ptr 值被覆盖,因此当您最终调用 shared_from_this.

时它包含一个过期值

标准可能禁止这种行为,但我认为更可能是允许这种行为的意图,并且在这个公认的小众角落案例中所有权的语义有点不明确。该标准确实注意到 "The shared_ptr constructors that create unique pointers can detect the presence of an enable_shared_from_this base and assign the newly created shared_ptr to its __weak_this member." ([util.smartptr.enab]/11)。尽管注释是非规范的,但我认为它说明了标准的意图。

您可以通过创建一个不共享所有权但仍指向 sharedFoo:

的真正空 shared_ptr 来完全避免该问题
std::shared_ptr<Foo> nonDeletingSharedFoo(std::shared_ptr<Foo>(), sharedFoo.get());

这利用了 "aliasing" 构造函数:

template<class Y> shared_ptr(const shared_ptr<Y>& r, T* p) noexcept;

创建一个 shared_ptrr 共享所有权,在本例中是一个空的默认构造 shared_ptr,并指向 p。结果是一个空的(非拥有的)shared_ptr,指向与 sharedFoo.

相同的对象

您有责任确保此类非拥有指针在引用对象的生命周期结束后永远不会被解除引用。清理设计可能会更好,以便您真正共享所有权,或者使用原始指针而不是 "non-owning shared_ptr" hack。