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

问题描述 投票:2回答:3

考虑以下代码:

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上。

c++ c++11 shared-ptr c++17 c++-standard-library
3个回答
7
投票

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

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


5
投票

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

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

0
投票

The issue is fundamental in the design (not technical details)

无论您使用的C ++标准版本的具体规格如何,您要做的事情都是不可能的。知道shared_from_this规范的精细细节不需要得出结论代码包含设计矛盾:只需要理解意图,即获取shared_ptr<A>this,在一个自动对象a上调用的成员函数中,足以确定设计是错误的。

事实上,任何尝试制作拥有智能指针(包括但不限于unique_ptrshared_ptr)指向(拥有)具有“范围”生命周期的对象的对象,其生命周期由其范围定义。声明对象并以退出的方式结束:

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

是一个设计错误,因为:

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

这包括构建一个智能指针,该指针拥有(即承诺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

Safely sharing ownership with a smart pointer

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

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

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

Importance of weak copies of a shared owning manager

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

(非空)弱智能指针总是直接或间接地拥有智能指针的副本,直接或间接地复制获得所有权的原始智能指针。那个“弱引用”实际上是一个“强大的”拥有智能指针,指向有关智能指针的其他拥有副本的存在的信息:只要有一个弱智能指针,就有可能确定是否存在拥有智能指针,如果是这样,以获得副本,那就是使共享智能指针成为原始的精确副本(原始的生命周期可能已经结束了许多代的复制)。

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

The purpose of 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的“魔力”可能看起来很神秘,而且太神奇了以至于用户不必考虑它,但它实际上非常简单:它保留了weak_ptr的目的是原件的副本。显然它不能被构造成这样的副本,因为在构造std::enable_shared_from_this子对象时甚至不能初始化原始:有效的拥有智能指针只能引用完全构造的对象,因为它拥有它并负责它的破坏。 [即使有人作弊,在管理对象完全构建之前制作一个拥有的智能指针,因此可以破坏,拥有的智能指针会有过早破坏的风险(即使在正常的事件过程中它的寿命很长,它例如,可以缩短例外情况。)

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

只有当原始最终获得所有权时,如果管理对象,它可以与std::enable_shared_from_this勾结:原始shared_ptr的构建将设置weak_ptr内的所有std::enable_shared_from_this成员。这些组件之间的主动共谋是使这些东西工作的唯一方法。

只有当它可以返回原件的副本时,即在构造原件之后,才能调用shared_from_this,这仍然是用户的责任。

About fake (non owning) owning smart pointers

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.