在同一个对象上创建多个 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_ptr
与 r
共享所有权,在本例中是一个空的默认构造 shared_ptr
,并指向 p
。结果是一个空的(非拥有的)shared_ptr
,指向与 sharedFoo
.
相同的对象
您有责任确保此类非拥有指针在引用对象的生命周期结束后永远不会被解除引用。清理设计可能会更好,以便您真正共享所有权,或者使用原始指针而不是 "non-owning shared_ptr" hack。
下面是一些示例代码(在线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_ptr
与 r
共享所有权,在本例中是一个空的默认构造 shared_ptr
,并指向 p
。结果是一个空的(非拥有的)shared_ptr
,指向与 sharedFoo
.
您有责任确保此类非拥有指针在引用对象的生命周期结束后永远不会被解除引用。清理设计可能会更好,以便您真正共享所有权,或者使用原始指针而不是 "non-owning shared_ptr" hack。