“ enable_shared_from_this”的作用是什么?


349

enable_shared_from_this在阅读Boost.Asio示例时遇到了麻烦,在阅读了文档之后,我仍然迷失了如何正确使用它的方法。有人可以给我一个例子,解释使用此类的合理性。

Answers:


362

当您拥有全部时,它使您可以获取有效的shared_ptr实例。没有它,除非您已经拥有一个成员,否则您将无法获得to 。这个例子来自于enable_shared_from_this Boost文档thisthisshared_ptrthis

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

即使没有成员实例,该方法也会f()返回有效的shared_ptr。请注意,您不能简单地这样做:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}

此共享指针返回的共享指针的引用计数将不同于“正确”的共享指针,并且其中的一个指针最终将丢失并在删除对象时保持悬挂的引用。

enable_shared_from_this已成为C ++ 11标准的一部分。您也可以从那里获得它,也可以从中获得它。


202
+1。关键点在于,仅返回shared_ptr <Y>(this)的“显而易见”技术已被破坏,因为这最终会创建多个具有单独引用计数的不同的shared_ptr对象。因此,您绝不能从同一原始指针创建多个shared_ptr 。
j_random_hacker

3
应当注意,在C ++ 11及更高版本中如果继承自,原始指针上使用构造函数是完全有效的我不知道 Boost的语义是否已更新以支持这一点。std::shared_ptr std::enable_shared_from_this
马修(马修)

6
@MatthewHolder您对此有报价吗?在cppreference.com上,我读到“ std::shared_ptr为一个已经由另一个对象管理的对象构造a std::shared_ptr不会参考内部存储的弱引用,因此会导致未定义的行为。” (en.cppreference.com/w/cpp/memory/enable_shared_from_this
托尔比约恩Lindeijer

5
你为什么不能做shared_ptr<Y> q = p呢?
Dan M.

2
@ThorbjørnLindeijer,您是对的,它是C ++ 17和更高版本。在发布之前,某些实现确实遵循C ++ 16语义。从C ++ 11到C ++ 14的正确处理应使用std::make_shared<T>
马修

198

来自Dobbs博士关于弱指针的文章,我认为这个例子更容易理解(来源:http//drdobbs.com/cpp/184402026):

...这样的代码无法正常工作:

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

两者都不 shared_ptr对象知道另一个,因此它们都将在销毁时尝试释放资源。这通常会导致问题。

同样,如果成员函数需要一个shared_ptr拥有正在调用的对象的对象,则它不能只是动态地创建对象:

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

该代码与前面的示例存在相同的问题,尽管形式更为精巧。构造它时,shared_ptr对象sp1拥有新分配的资源。成员函数内部的代码S::dangerous不知道该shared_ptr对象,因此shared_ptr它返回的对象与有所不同sp1。将新shared_ptr对象复制到sp2无济于事;当sp2超出范围时,它将释放资源,而当sp1超出范围时,它将再次释放资源。

避免此问题的方法是使用类模板enable_shared_from_this。模板采用一个模板类型参数,这是定义托管资源的类的名称。该类又必须从模板公开派生;像这样:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}

执行此操作时,请记住,调用对象shared_from_this必须归shared_ptr对象所有。这行不通:

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}

15
谢谢,这说明比当前接受的答案更好地解决了问题。
goertzenator 2013年

2
+1:好答案。作为一个的一边,而不是shared_ptr<S> sp1(new S);它可以优选地使用shared_ptr<S> sp1 = make_shared<S>();,例如参见stackoverflow.com/questions/18301511/...
阿伦

4
我很确定应该读最后一行,shared_ptr<S> sp2 = p->not_dangerous();因为这里的陷阱是您必须shared_from_this()在第一次调用之前以常规方式创建一个shared_ptr !这真的很容易出错!C ++ 17之前,它是UB调用shared_from_this()只有一个shared_ptr的已经创建的正常方式之前:auto sptr = std::make_shared<S>();shared_ptr<S> sptr(new S());。值得庆幸的是,从C ++ 17开始,这样做会抛出异常。
AnorZaken'9


2
@AnorZaken好点。如果您提交了编辑请求以进行修复,这将很有用。我刚刚这样做。对于发布者,另一个有用的事情是不要选择主观的,上下文相关的方法名称!
underscore_d

30

这是我的解释,从具体的角度来看(最主要的答案不是“点击”我)。*请注意,这是调查Visual Studio 2012随附的shared_ptr和enable_shared_from_this源的结果。也许其他编译器以不同的方式实现enable_shared_from_this ... *

enable_shared_from_this<T>添加一个私有weak_ptr<T>实例,T该实例包含的实例的“ 一个真实引用计数T

所以,当你第一次创建一个shared_ptr<T>到一个新的T *,即T *的内部weak_ptr的获取与1.新的引用计数初始化的shared_ptr基本备份到这个weak_ptr

T然后可以在其方法中,调用shared_from_this以获得的一个实例shared_ptr<T>背到同一内部存储的参考计数。这样,您总是在一个地方T*存储ref-count,而不是有多个shared_ptr彼此不认识的实例,并且每个实例都认为它们是shared_ptr负责ref-count的实例,并在引用T时将其删除计数达到零。


1
这是正确的,真正重要的部分是So, when you first create...因为这是一个要求(正如您所说的,在将对象指针传递到shared_ptr ctor之前,不初始化weak_ptr!),如果您是不小心 如果在调用之前未创建shared_ptr,则shared_from_this您会得到UB-同样,如果创建多个shared_ptr,也会获得UB。你必须以某种方式确保你创建一个shared_ptr 正是一次。
AnorZaken'9

2
换言之的整体思路enable_shared_from_this是脆开始以来的一点是能够得到一个shared_ptr<T>T*,但在现实中,当你得到一个指向T* t它通常是不可靠关于它已被共享或不承担任何东西,做出错误的猜测是UB。
AnorZaken'9

内部的soft_ptr被初始化为refcount为1 ”到T的弱ptr是对T的非拥有智能ptr。弱ptr是拥有足够智能信息的拥有智能ref,以使拥有ptr成为其他拥有ptr的“副本”。弱ptr没有参考计数。它可以访问引用计数,就像所有引用的引用一样。
curiousguy

3

请注意,使用boost :: intrusive_ptr不会遇到此问题。这通常是解决此问题的更方便的方法。


是的,但是enable_shared_from_this允许您使用专门接受的API shared_ptr<>。在我看来,这样的API通常做错了(因为最好让栈中更高的东西拥有内存),但是如果您被迫使用这样的API,这是一个不错的选择。
cdunn2001 2013年

2
最好尽可能地保持标准。
谢尔盖

3

在c ++ 11及更高版本中,这是完全相同的:它使您能够以this共享指针的形式返回,因为this它为您提供了原始指针。

换句话说,它使您可以像这样转换代码

class Node {
public:
    Node* getParent const() {
        if (m_parent) {
            return m_parent;
        } else {
            return this;
        }
    }

private:

    Node * m_parent = nullptr;
};           

到这个:

class Node : std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getParent const() {
        std::shared_ptr<Node> parent = m_parent.lock();
        if (parent) {
            return parent;
        } else {
            return shared_from_this();
        }
    }

private:

    std::weak_ptr<Node> m_parent;
};           

仅当这些对象始终由进行管理时,此方法才有效shared_ptr。您可能需要更改界面以确保确实如此。
curiousguy

1
你是绝对正确的@curiousguy。这不用说。我还喜欢在定义公共API时对我所有shared_ptr进行类型定义,以提高可读性。例如,代替std::shared_ptr<Node> getParent const(),我通常将其公开为NodePtr getParent const()。如果您绝对需要访问内部原始指针(最佳示例:处理C库),那是std::shared_ptr<T>::get我不愿提及的原因,因为由于错误的原因,这个原始指针访问器使用了太多次。
mchiasson

-3

另一种方法是将weak_ptr<Y> m_stub成员添加到中class Y。然后写:

shared_ptr<Y> Y::f()
{
    return m_stub.lock();
}

当您无法更改派生的类时(例如扩展其他人的图书馆),此功能很有用。不要忘记初始化成员,例如通过初始化成员,m_stub = shared_ptr<Y>(this)即使在构造函数期间它也是有效的。

如果在继承层次结构中有更多这样的存根,那是可以的,这不会阻止对象的破坏。

编辑:正如用户nobar正确指出的那样,代码将在分配完成并销毁临时变量时销毁Y对象。因此,我的答案不正确。


4
如果您的意图是产生一个shared_ptr<>不会删除其指针的,那就太过分了。你可以简单地说,return shared_ptr<Y>(this, no_op_deleter);这里no_op_deleter是一元函数对象服用Y*,无所事事。
约翰·兹温克

2
这似乎不太可能是可行的解决方案。 m_stub = shared_ptr<Y>(this)将由此构造并立即销毁一个临时shared_ptr。该语句结束后,this将被删除,并且所有后续引用都将悬空。
nobar 2011年

2
作者承认这个答案是错误的,因此他可能只是将其删除。但是他最后登录4.5年,所以不太可能这样做-具有更高能力的人可以删除此红色鲱鱼吗?
汤姆·古德费罗

如果您查看的实现enable_shared_from_this,它会保留a weak_ptr本身(由ctor填充),并shared_ptr在调用时作为a返回shared_from_this。换句话说,您正在复制enable_shared_from_this已经提供的内容。
mchiasson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.