为什么调用 shared_from_this 调用 std::terminate
Why calling shared_from_this calls std::terminate
考虑这段代码:
class A : public std::enable_shared_from_this<A>
{
public:
std::shared_ptr<A> f()
{
return shared_from_this();
}
};
int main()
{
A a;
std::shared_ptr<A> ptr = a.f();
}
此代码于 Visual Studio 2017 年终止。我想我在这里做错了什么。谁能帮我这个?我想要一个由 shared_from_this() 创建的 shared_ptr。
您不能使用 shared_from_this
生成 new 共享指针。您已经必须有一个现有的共享指针才能获得一个新的:
std::shared_ptr a(new A());
auto ptr = a->f(); // ok, 'ptr' shares ownership of newed object with 'a'
因为a
不属于共享指针。来自 cppreference:
It is permitted to call shared_from_this only on a previously shared object, i.e. on an object managed by std::shared_ptr. Otherwise the behavior is undefined (until C++17)std::bad_weak_ptr is thrown (by the shared_ptr constructor from a default-constructed weak_this) (since C++17).
问题是设计的根本(不是技术细节)
无论您使用的 C++ 标准版本的确切规范是什么,您尝试做的事情都是不可能的。不需要了解 shared_from_this
规范的细节就可以得出代码包含设计矛盾的结论: 只需了解其意图,即获得 shared_ptr<A>
到 this
,在自动对象a
上调用的成员函数中,足以判断设计有误
事实上,任何尝试创建指向(那个拥有)一个具有 "scoped" 生命周期的对象,该对象的生命周期由对象声明的范围定义并以退出某些东西结束:
- 自动对象(退出范围时生命周期结束),
- 命名空间范围对象,classes 的静态对象成员(生命周期在程序退出时结束),
- classes 的非静态成员(当包含 class 对象的析构函数体退出时生命周期结束),
是设计错误,因为:
- 这些对象不是用允许对结果调用
delete
的任何变体 new
(普通 operator new
或 nothrow
变体)创建的;
- C++ 允许您销毁此类对象的唯一情况是销毁之后(最好立即)通过放置新的具有相同完整类型的对象进行重建,这显然不是拥有智能指针的工作;
- 编译器将在程序执行到达退出点(退出作用域、退出析构函数、或
std::exit
、或 return
from main 时销毁该对象),无论如何(即使您拥有智能指针已经处理好了);试图销毁一个已经销毁的对象是不行的。
这包括构建一个拥有(即承诺 delete
)动态分配的 class 实例成员的智能指针:
struct A {
int m;
};
#define OK 1
void f() {
A *p = new A;
#if OK
std::shared_ptr<A> own (p); // fine
#else
std::shared_ptr<int> own (&p->m); // bad
#endif
}
这里p
指向的对象的生命周期是动态管理的;由程序代码明确确定的销毁时间以及唯一成员 m
的生命周期与 A
对象的生命周期存在内在联系;但成员本身不需要显式销毁,也不会被删除。如果 OK
预处理器常量为 1,则一切正常;如果它为 0,则您正在尝试显式管理成员的生命周期,这是不合理的。
关于术语 "explicit" 对 delete
的调用:尽管 delete
运算符从未出现在代码中,但它的调用隐含在 std::shared_ptr
的使用中;换句话说,std::shared_ptr
显式使用 delete
,因此使用 std::shared_ptr
(或其他类似的拥有智能指针)是间接使用 delete
.
使用智能指针安全共享所有权
共享 shared_ptr
所有权的唯一安全方法是直接或间接从另一个 shared_ptr
中获取一个。 这是最基本的shared_ptr
的 属性:指向同一个对象的所有实例必须可追溯到用原始指针(或者 make_shared
)构造的那个实例。
这是所有权信息(通常是引用计数,但如果您喜欢低效的实现,则可以是链接列表)这一事实的直接结果 不 在托管对象,但在 shared_ptr
创建的信息块内。这不仅仅是 std::shared_ptr
的 属性,这是所有这些外部管理对象的生活事实,没有全局注册表,就不可能找到管理器。
这些智能指针的基本设计决策是不需要修改托管对象就可以使用智能指针;因此它们可以用于现有数据类型(包括基本类型)。
共享拥有管理器的弱副本的重要性
shared_ptr
的基础 属性 会产生一个问题:因为每一层代码(可能需要调用需要拥有指针的函数)都需要保留 shared_ptr
周围,这可以创建一个拥有智能指针的网络,其中一些智能指针可能驻留在一个对象中,该对象的生命周期由另一个生命周期由该智能指针管理;因为智能指针的基本规范是在销毁负责其销毁的智能指针的所有副本之前,托管对象不会被销毁,所以这些对象永远不会被销毁(按照规定;这是 not 引用计数的特定实现选择的结果)。有时需要一个不影响托管对象生命周期的拥有智能指针的副本,因此需要弱智能指针。
(非空)弱智能指针总是直接或间接地是拥有智能指针的副本,直接或间接地是获得所有权的原始智能指针的副本。那个"weak reference"实际上是一个"strong"拥有智能指针指向其他拥有智能指针副本是否存在的信息:只要有弱智能指针,就可以判断是否存在是一个实时拥有的智能指针,如果是这样获得一个副本,那就是制作一个共享智能指针,它是原始的精确副本(原始的生命周期可能已经结束很多代副本了)。
弱智能指针的唯一目的是获得原始的副本。
目的std::enable_shared_from_this
std::enable_shared_from_this
的唯一用途是获取原件的副本shared_ptr
;这意味着这样的拥有智能指针必须已经存在。不会制作新的原件(另一个智能指针取得所有权)。
仅将 std::enable_shared_from_this
用于仅打算由 shared_ptr
.
管理的 classes
std::enable_shared_from_this
的详情
说了这么多关于理论原理的内容,了解 std::enable_shared_from_this
包含的内容、正确使用它如何产生 shared_ptr
(以及为什么不能期望它在任何环境中工作)是很有用的其他情况)。
std::enable_shared_from_this
的"magic"看似神秘,神奇到用户不用费心思,其实非常简单:它保留了一个weak_ptr
旨在成为 原件 的副本。显然它不能构造为这样的副本,因为 原始 在构造 std::enable_shared_from_this
子对象时甚至无法初始化:有效的拥有智能指针只能引用完全构造的对象,因为它拥有它并负责销毁它。 [即使在托管对象完全构造之前通过某些作弊创建了一个拥有智能指针,因此可以破坏,拥有智能指针将有过早破坏的风险(即使在正常事件过程中它的生命周期很长,它例如,可以通过异常缩短。]
因此 std::enable_shared_from_this
中的数据成员初始化本质上是默认初始化:此时 "weak pointer" 为空。
只有当原始最终获得托管对象的所有权时,它才能与std::enable_shared_from_this
勾结: 原来shared_ptr
会一劳永逸地设置std::enable_shared_from_this
里面的weak_ptr
成员。这些组件之间的积极勾结是使这些东西起作用的唯一方法。
用户有责任仅在可能return 原始的副本时才调用shared_from_this
,即在原版已构建
关于假(非拥有)拥有智能指针
假拥有智能指针是一种永远不会进行清理的智能指针:仅在名义上拥有智能指针。它们是 "owning" 智能指针的特例,以不执行破坏或清理的方式使用。这表面上意味着它们可以用于生命周期已预先确定(并且足够长)并且需要假装拥有智能指针的对象;与真正拥有的智能指针不同,保留副本不会延长对象的生命周期,因此生命周期最好真的很长。 (因为拥有的智能指针的副本可以存储在全局变量中,所以在 main
的 return
之后该对象仍然可以预期是存活的。)
这些非拥有者在条款上显然是矛盾的,很少是安全的(但在少数情况下可以证明是安全的)。
他们很少解决合理的问题(不是非常糟糕的设计的直接后果):接口中的 shared_ptr
意味着接收者期望能够延长生命周期被管理的对象。
考虑这段代码:
class A : public std::enable_shared_from_this<A>
{
public:
std::shared_ptr<A> f()
{
return shared_from_this();
}
};
int main()
{
A a;
std::shared_ptr<A> ptr = a.f();
}
此代码于 Visual Studio 2017 年终止。我想我在这里做错了什么。谁能帮我这个?我想要一个由 shared_from_this() 创建的 shared_ptr。
您不能使用 shared_from_this
生成 new 共享指针。您已经必须有一个现有的共享指针才能获得一个新的:
std::shared_ptr a(new A());
auto ptr = a->f(); // ok, 'ptr' shares ownership of newed object with 'a'
因为a
不属于共享指针。来自 cppreference:
It is permitted to call shared_from_this only on a previously shared object, i.e. on an object managed by std::shared_ptr. Otherwise the behavior is undefined (until C++17)std::bad_weak_ptr is thrown (by the shared_ptr constructor from a default-constructed weak_this) (since C++17).
问题是设计的根本(不是技术细节)
无论您使用的 C++ 标准版本的确切规范是什么,您尝试做的事情都是不可能的。不需要了解 shared_from_this
规范的细节就可以得出代码包含设计矛盾的结论: 只需了解其意图,即获得 shared_ptr<A>
到 this
,在自动对象a
上调用的成员函数中,足以判断设计有误
事实上,任何尝试创建指向(那个拥有)一个具有 "scoped" 生命周期的对象,该对象的生命周期由对象声明的范围定义并以退出某些东西结束:
- 自动对象(退出范围时生命周期结束),
- 命名空间范围对象,classes 的静态对象成员(生命周期在程序退出时结束),
- classes 的非静态成员(当包含 class 对象的析构函数体退出时生命周期结束),
是设计错误,因为:
- 这些对象不是用允许对结果调用
delete
的任何变体new
(普通operator new
或nothrow
变体)创建的; - C++ 允许您销毁此类对象的唯一情况是销毁之后(最好立即)通过放置新的具有相同完整类型的对象进行重建,这显然不是拥有智能指针的工作;
- 编译器将在程序执行到达退出点(退出作用域、退出析构函数、或
std::exit
、或return
from main 时销毁该对象),无论如何(即使您拥有智能指针已经处理好了);试图销毁一个已经销毁的对象是不行的。
这包括构建一个拥有(即承诺 delete
)动态分配的 class 实例成员的智能指针:
struct A {
int m;
};
#define OK 1
void f() {
A *p = new A;
#if OK
std::shared_ptr<A> own (p); // fine
#else
std::shared_ptr<int> own (&p->m); // bad
#endif
}
这里p
指向的对象的生命周期是动态管理的;由程序代码明确确定的销毁时间以及唯一成员 m
的生命周期与 A
对象的生命周期存在内在联系;但成员本身不需要显式销毁,也不会被删除。如果 OK
预处理器常量为 1,则一切正常;如果它为 0,则您正在尝试显式管理成员的生命周期,这是不合理的。
关于术语 "explicit" 对 delete
的调用:尽管 delete
运算符从未出现在代码中,但它的调用隐含在 std::shared_ptr
的使用中;换句话说,std::shared_ptr
显式使用 delete
,因此使用 std::shared_ptr
(或其他类似的拥有智能指针)是间接使用 delete
.
使用智能指针安全共享所有权
共享 shared_ptr
所有权的唯一安全方法是直接或间接从另一个 shared_ptr
中获取一个。 这是最基本的shared_ptr
的 属性:指向同一个对象的所有实例必须可追溯到用原始指针(或者 make_shared
)构造的那个实例。
这是所有权信息(通常是引用计数,但如果您喜欢低效的实现,则可以是链接列表)这一事实的直接结果 不 在托管对象,但在 shared_ptr
创建的信息块内。这不仅仅是 std::shared_ptr
的 属性,这是所有这些外部管理对象的生活事实,没有全局注册表,就不可能找到管理器。
这些智能指针的基本设计决策是不需要修改托管对象就可以使用智能指针;因此它们可以用于现有数据类型(包括基本类型)。
共享拥有管理器的弱副本的重要性
shared_ptr
的基础 属性 会产生一个问题:因为每一层代码(可能需要调用需要拥有指针的函数)都需要保留 shared_ptr
周围,这可以创建一个拥有智能指针的网络,其中一些智能指针可能驻留在一个对象中,该对象的生命周期由另一个生命周期由该智能指针管理;因为智能指针的基本规范是在销毁负责其销毁的智能指针的所有副本之前,托管对象不会被销毁,所以这些对象永远不会被销毁(按照规定;这是 not 引用计数的特定实现选择的结果)。有时需要一个不影响托管对象生命周期的拥有智能指针的副本,因此需要弱智能指针。
(非空)弱智能指针总是直接或间接地是拥有智能指针的副本,直接或间接地是获得所有权的原始智能指针的副本。那个"weak reference"实际上是一个"strong"拥有智能指针指向其他拥有智能指针副本是否存在的信息:只要有弱智能指针,就可以判断是否存在是一个实时拥有的智能指针,如果是这样获得一个副本,那就是制作一个共享智能指针,它是原始的精确副本(原始的生命周期可能已经结束很多代副本了)。
弱智能指针的唯一目的是获得原始的副本。
目的std::enable_shared_from_this
std::enable_shared_from_this
的唯一用途是获取原件的副本shared_ptr
;这意味着这样的拥有智能指针必须已经存在。不会制作新的原件(另一个智能指针取得所有权)。
仅将 std::enable_shared_from_this
用于仅打算由 shared_ptr
.
std::enable_shared_from_this
的详情
说了这么多关于理论原理的内容,了解 std::enable_shared_from_this
包含的内容、正确使用它如何产生 shared_ptr
(以及为什么不能期望它在任何环境中工作)是很有用的其他情况)。
std::enable_shared_from_this
的"magic"看似神秘,神奇到用户不用费心思,其实非常简单:它保留了一个weak_ptr
旨在成为 原件 的副本。显然它不能构造为这样的副本,因为 原始 在构造 std::enable_shared_from_this
子对象时甚至无法初始化:有效的拥有智能指针只能引用完全构造的对象,因为它拥有它并负责销毁它。 [即使在托管对象完全构造之前通过某些作弊创建了一个拥有智能指针,因此可以破坏,拥有智能指针将有过早破坏的风险(即使在正常事件过程中它的生命周期很长,它例如,可以通过异常缩短。]
因此 std::enable_shared_from_this
中的数据成员初始化本质上是默认初始化:此时 "weak pointer" 为空。
只有当原始最终获得托管对象的所有权时,它才能与std::enable_shared_from_this
勾结: 原来shared_ptr
会一劳永逸地设置std::enable_shared_from_this
里面的weak_ptr
成员。这些组件之间的积极勾结是使这些东西起作用的唯一方法。
用户有责任仅在可能return 原始的副本时才调用shared_from_this
,即在原版已构建
关于假(非拥有)拥有智能指针
假拥有智能指针是一种永远不会进行清理的智能指针:仅在名义上拥有智能指针。它们是 "owning" 智能指针的特例,以不执行破坏或清理的方式使用。这表面上意味着它们可以用于生命周期已预先确定(并且足够长)并且需要假装拥有智能指针的对象;与真正拥有的智能指针不同,保留副本不会延长对象的生命周期,因此生命周期最好真的很长。 (因为拥有的智能指针的副本可以存储在全局变量中,所以在 main
的 return
之后该对象仍然可以预期是存活的。)
这些非拥有者在条款上显然是矛盾的,很少是安全的(但在少数情况下可以证明是安全的)。
他们很少解决合理的问题(不是非常糟糕的设计的直接后果):接口中的 shared_ptr
意味着接收者期望能够延长生命周期被管理的对象。