在同一对象上创建多个shared_ptr“族”时shared_from_this的意外行为

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

本文关键字:shared from this 意外 ptr 对象 创建      更新时间:2023-10-16

下面是一些示例代码(在线在这里(:

#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的弱计数。我认为这是因为当指向类型实现enable_shared_from_this时,shared_ptr构造函数和/或析构函数会做一些特殊的事情。

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

你在这里处于灰色地带:enable_shared_from_this通常是通过让shared_ptr构造函数来实现的,这些构造函数获得指向派生对象的原始指针的所有权enable_shared_from_this设置对象中包含的weak_ptr。因此,后来呼吁shared_from_this()有东西要回报。当您"重定父级"时sharedFoo原始weak_ptr值将被覆盖,以便在您最终调用 shared_from_this 时它包含过期的值。

这种行为可能是标准所禁止的,但我认为更有可能是允许它的意图,并且在这个公认的利基角落案例中,所有权的语义有点被低估了。该标准确实指出,"创建唯一指针的shared_ptr构造函数可以检测enable_shared_from_this基的存在,并将新创建的shared_ptr分配给其__weak_this成员。([util.smartptr.enab]/11(。尽管注释是非规范性的,但我认为它说明了标准的意图。

您可以通过创建一个真正空的shared_ptr来完全避免这个问题,该不共享所有权,但仍指向sharedFoo

std::shared_ptr<Foo> nonDeletingSharedFoo(std::shared_ptr<Foo>(), sharedFoo.get());

这利用了"别名"构造函数:

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

这将创建一个与r共享所有权的shared_ptr,在本例中是一个空的默认构造shared_ptr,并指向p。结果是一个空的(非拥有的(shared_ptr,它指向与sharedFoo相同的对象。

您有责任确保在引用对象的生存期结束后,永远不会取消引用此类非拥有指针。清理设计可能会更好,以便您真正共享所有权,或者使用原始指针而不是"非拥有shared_ptr"黑客。

当你使用 get from sharedFoo (sharedFoo.get((( 时,你会得到shared_ptr包含的地址。因此,nonDeletingSharedFoo无法访问shared_ptr,当您重置nonDeletingSharedFoo时,您将释放地址内存。所以 sharedFoo 的对象现在不存在了。