为什么调用shared_from_this调用 std::终止



请考虑以下代码:

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。

因为a不是共享指针的所有者。从 cpp 首选项:

只允许在以前共享的对象上调用shared_from_this,即在由 std::shared_ptr 管理的对象上。否则,行为是未定义的(直到 C++17)std::bad_weak_ptr 被抛出(由默认构造weak_this中的shared_ptr构造函数引发)(自 C++17 以来)。

不能使用shared_from_this生成新的共享指针。您已经必须有一个现有的共享指针才能获得新的共享指针:

std::shared_ptr a(new A());
auto ptr = a->f(); // ok, 'ptr' shares ownership of newed object with 'a'

这个问题是设计中的基础(不是技术细节)

无论您使用的C++标准版本的确切规格是什么,您尝试做的事情都是不可能的。了解shared_from_this规范的细节并不需要得出代码包含设计矛盾的结论:只需了解意图,即在调用a(一个自动对象)的成员函数内获得thisshared_ptr<A>,就足以确定设计是错误的。

事实上,任何试图拥有智能指针(包括但不限于unique_ptrshared_ptr)的尝试都指向(拥有)具有"作用域"生存期的对象,该对象其生存期由对象的声明范围定义,并以退出某些内容结束:

  • 自动对象(退出范围时生存期结束),
  • 命名空间范围对象,类的静态对象成员(生存期在程序退出时结束),
  • 的非静态成员(当包含类对象的析构函数的主体退出时,生存期结束),

是一个设计错误,因为:

  • 这些对象不是使用任何允许对结果调用delete的变体new(纯operator newnothrow变体)创建的);
  • C++允许您销毁此类对象的唯一情况是通过销毁,然后(最好立即)重建,并放置具有相同完整类型的对象的新位置,这显然不是拥有智能指针的工作;
  • 当程序执行到达退出点(退出作用域、退出析构函数或std::exit或从 Mainreturn)时,编译器将销毁该对象,无论如何(即使您拥有的智能指针已经处理了它);尝试销毁已经销毁的对象是不行的。

这包括构造一个智能指针,该指针拥有(即承诺delete)动态分配的类实例的成员:

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,则您正在尝试显式管理成员的生存期,这是不合理的。

关于术语"显式"调用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的副本,这可以创建一个拥有智能指针的网络,其中一些可能驻留在一个对象中,该对象生存期由另一个对象管理,而该对象生命周期由该确切的智能指针管理;因为智能指针基本规范是托管对象在销毁负责销毁的智能指针的所有副本被销毁之前不会被销毁,这些对象永远不会被销毁(如指定;这不是引用计数的特定实现选择的结果)。有时需要不妨碍影响托管对象生存期的物种的拥有智能指针的副本,因此需要弱智能指针。

(非空)弱智能指针始终直接或间接是拥有智能指针的副本,直接或间接是获得所有权的原始智能指针的副本。这个"弱引用"实际上是一个"强"拥有智能指针,指向有关智能指针的其他拥有副本存在的信息:只要有一个弱智能指针,就有可能确定是否存在一个活的拥有智能指针,如果是这样,则获得一个副本,即制作一个共享的智能指针,它是原始指针的精确副本(原始的生命周期可能有结束了许多代副本之前)。

弱智能指针的唯一目的是获取原件的此类副本。

std::enable_shared_from_this的目的

std::enable_shared_from_this的唯一用途是获取原始shared_ptr的副本;这意味着这种拥有智能指针必须已经存在。不会制作新的原件(另一个智能指针获得所有权)。

仅对仅由shared_ptr管理的类使用std::enable_shared_from_this

std::enable_shared_from_this详情

综上所述,了解std::enable_shared_from_this包含什么,正确使用时如何产生shared_ptr(以及为什么不能期望它在任何其他情况下起作用)是有用的。

std::enable_shared_from_this的"魔力"可能看起来很神秘,而且太神奇了,以至于用户不必考虑它,但它实际上非常简单:它保留了一个旨在成为原件副本的weak_ptr。显然,它不能被构造为这样的副本,因为在构造std::enable_shared_from_this子对象时甚至无法初始化原始对象:一个有效的拥有智能指针只能引用一个完全构造的对象,因为它拥有它并负责它的销毁。[即使通过某些作弊,在托管对象完全构造之前制作了拥有智能指针,因此是可破坏的,拥有智能指针也会有过早破坏的风险(即使在正常事件过程中,它的生存期很长,也可以通过例外来缩短)。

因此,std::enable_shared_from_this中的数据成员初始化本质上是默认初始化:"弱指针"在这一点上为空。

只有当原始对象最终获得所有权时,如果托管对象,它才能与std::enable_shared_from_this串通原始shared_ptr的构造将一劳永逸地设置std::enable_shared_from_this内部的所有weak_ptr成员。这些组件之间的主动勾结是使这些东西工作的唯一方法。

用户仍然有责任仅在可能返回原始副本(即在构造原始副本之后)时才调用shared_from_this

关于假(非拥有)拥有智能指针

假拥有智能指针是一种永远不会进行清理的指针:仅名义上拥有智能指针。它们是"拥有"智能指针的特殊情况,其使用方式不会执行销毁或清理。这表面上意味着它们可以用于生命周期是预先确定的(并且足够长)的对象,并且需要有一个假装拥有智能指针的对象;与真正的拥有智能指针不同,保留副本不会延长对象的生存期,因此生存期最好很长。(由于拥有智能指针的副本可以存储在全局变量中,因此在从mainreturn后,该对象仍可能处于活动状态。

这些非拥有所有者显然在条款上是矛盾的,很少安全(但在少数情况下可以证明是安全的)。

它们很少解决合法问题(这不是非常糟糕的设计的直接后果):接口中的shared_ptr意味着接收方希望能够延长托管对象的生存期。

相关内容

最新更新