为什么调用shared_from_this调用std :: terminate

时间:2018-05-10 10:24:33

标签: c++ c++11 shared-ptr c++17 c++-standard-library

考虑以下代码:

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。

2 个答案:

答案 0 :(得分:7)

因为a不是共享指针所拥有的。来自cppreference

  

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

答案 1 :(得分:0)

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

无论您使用的C ++标准版本的具体规格是什么,您尝试做的事情都是不可能的。了解shared_from_this规范的详细细节并不需要得出结论,代码包含设计矛盾:只是理解意图,即获得shared_ptr<A>到{{} 1}},在一个名为this的成员函数中,一个自动对象,足以确定设计是错误的。

事实上,任何尝试制作拥有智能指针(包括但不限于aunique_ptr)指向(拥有)某个对象的对象&#34;作用域&#34;生命周期,这是一个对象,其生命周期由对象声明的范围定义,并以退出的东西结束:

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

是一个设计错误,因为:

  • 这些对象未使用允许在结果上调用shared_ptr的任何变体new(普通operator newnothrow变体)创建;
  • C ++允许你销毁这种对象的唯一情况是通过使用相同完整类型的对象的放置new进行重建来跟踪(最好是立即),这显然不是拥有智能指针的工作;
  • 编译器将在程序执行到达退出点时退出该对象(退出范围,退出析构函数,或deletestd::exit来自main),无论如何(即使您拥有智能指针)已经照顾好了);试图摧毁已经破坏的物体是不行的。

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

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预处理器常量为1,则一切都很好;如果它为0,则表示您正在尝试明确管理成员的生命周期,这是不合理的。

关于术语&#34;明确&#34;致电OK:虽然delete运算符从未出现在代码中,但其调用隐含在使用delete;换句话说,std::shared_ptr明确使用std::shared_ptr,因此使用delete(或其他类似的拥有智能指针)是间接使用std::shared_ptr

使用智能指针安全地共享所有权

分享delete所有权的唯一安全方法是直接或间接地从另一个shared_ptr创建一个。这是{{1}的基本属性指向一个对象的所有实例必须可追溯到一个用原始指针构建的实例(或者用shared_ptr)。

这是由于所有权信息(通常是引用计数,但如果您喜欢低效的实现,可能是链接列表)直接导致托管对象内的,但在由shared_ptr创建的信息块。这不仅仅是make_shared的属性,它是所有这些外部管理对象的生命现实,没有全局注册表,找不到管理员是不可能的。

这些智能指针的基本设计决策是无需修改托管对象即可使用智能指针;因此它们可用于现有数据类型(包括基本类型)。

共享拥有管理器的弱副本的重要性

shared_ptr的基本属性会产生一个问题:因为每一层代码(可能需要调用需要拥有指针的函数)都需要保留std::shared_ptr的副本,这可以创建一个拥有智能指针的Web,其中一些可能驻留在一个对象中,该对象的生命周期由另一个由该精确智能指针管理的生命周期管理;因为智能指针基本规范是在破坏负责破坏的智能指针的所有副本之前不破坏托管对象,所以这些对象永远不会被销毁(如指定的那样;这是不是 a参考计数的特定实施选择的结果)。有时需要一个不具备影响托管对象生命周期的硬币的拥有智能指针的副本,因此需要弱智能指针。

一个(非空)弱智能指针总是直接或间接地拥有一个拥有智能指针的副本,直接或间接地复制获得所有权的原始智能指针。那个&#34;弱参考&#34;实际上是一个强大的&#34;拥有智能指针指向有关智能指针的其他拥有副本的存在的信息:只要存在弱智能指针,就可以确定是否存在实时拥有智能指针,如果是,则获取副本,这是一个共享的智能指针,它是原始的精确副本(原始的生命周期可能已经结束了许多代的复制之前)。

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

shared_ptr

的目的

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

仅针对仅由std::enable_shared_from_this管理的课程使用shared_ptr

std::enable_shared_from_this

的详情

关于理论原则所说的一切,有助于理解shared_ptr包含什么,如果正确使用它会如何产生std::enable_shared_from_this(以及为什么它不能被认为有效)在任何其他情况下)。

&#34;魔法&#34; std::enable_shared_from_this的{​​{1}}可能看起来很神秘,太神奇了以至于用户不必考虑它,但它实际上非常简单:它保留shared_ptr打算成为副本原始。显然它不能构造为这样的副本,因为原始的甚至在构造std::enable_shared_from_this子对象时甚至无法初始化:有效的拥有智能指针只能引用完全构造的对象,因为它拥有它并负责销毁它。 [即使有人作弊,在管理对象完全构建之前制作一个拥有的智能指针,因此可以破坏,拥有的智能指针会有过早破坏的风险(即使在正常的事件过程中它的寿命很长,它例如,可以缩短例外情况。]

因此,weak_ptr中的数据成员初始化本质上是默认初始化:&#34;弱指针&#34;那时为空。

只有当原始最终获得托管对象的所有权时,它才能std::enable_shared_from_this串联:构建原始 std::enable_shared_from_this将在std::enable_shared_from_this内设置一次shared_ptr成员。这些组件之间的主动勾结是使这些组件有效的唯一方法。

用户有责任仅在可能会返回原始的副本时调用weak_ptr,即之后原来的已经构建完毕。

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

虚假的拥有智能指针是一种不进行清理的指针:仅在名称中拥有智能指针。他们是&#34;拥有&#34;的特例。以这种方式使用的智能指针,不执行破坏或清理。这表面上意味着它们可以用于生命周期(足够长)并且需要假装拥有智能指针的对象;与真正拥有的智能指针不同,保留副本不会延长对象的生命周期,因此生命周期应该更长。 (因为拥有智能指针的副本可以存储在全局变量中,所以在std::enable_shared_from_this shared_from_this之后,该对象仍然可能存在。)

这些非拥有者显然是一个矛盾的术语,很少安全(但在少数情况下可以证明是安全的)。

他们很少解决一个合法的问题(一个不是设计非常糟糕的直接后果):接口中的return意味着接收者期望能够延长其生命周期托管对象。