`enable_shared_from_this`的用处是什么?

fid*_*ido 332 c++ boost tr1 boost-asio

我在enable_shared_from_this阅读Boost.Asio示例时跑了过来,在阅读完文档之后,我仍然因为如何正确使用它而迷失了方向.有人可以给我一个例子和/或说明何时使用这个课程是有道理的.

180*_*ION 347

它可以让你获得一个有效的shared_ptr实例this,当你拥有它时this.没有它,你就没有得到一个的方式shared_ptrthis,除非你已经有一个为会员.这个例子来自enable_shared_from_thisboost文档:

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
}
Run Code Online (Sandbox Code Playgroud)

方法f()返回一个有效的f(),即使它没有成员实例.请注意,您不能简单地这样做:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

返回的共享指针将具有与"正确"引用不同的引用计数,并且当删除对象时,其中一个将最终丢失并保持悬空引用.

shared_ptr已成为C++ 11标准的一部分.你也可以从那里以及从中获得它.

  • +1.关键点在于,仅仅返回shared_ptr <Y>(this)的"显而易见"技术被打破了,因为这最终创建了具有单独引用计数的多个不同的shared_ptr对象.因此,您必须永远不要从同一个原始指针**创建多个shared_ptr**. (186认同)
  • @MatthewHolder你有这个报价吗?在cppreference.com上我读到"为另一个`std :: shared_ptr`管理的对象构造`std :: shared_ptr`将不会查询内部存储的弱引用,从而导致未定义的行为." (http://en.cppreference.com/w/cpp/memory/enable_shared_from_this) (6认同)
  • 为什么你不能只做`shared_ptr <Y> q = p`? (3认同)
  • @丹M。可以,这就是为什么这个示例不是很有用。但它肯定有用例。当没有“q”并且您需要类内部的“p”时。 (3认同)
  • 应该注意的是,在_C++ 11和later_中,如果**它继承自`std :: enable_shared_from_this`,那么在_raw pointer_**上使用`std :: shared_ptr`构造函数是完全有效的**.**我不知道**Boost的语义是否已更新以支持此功能. (2认同)
  • @丹M。是的,你可以这样做,因为 p 和 q 都是共享指针。当您在类内部时,例如在我上面的方法 f() 中,需要启用_shared_from_this。这是一个简化的示例,但它显示了要点 (2认同)
  • @ThorbjørnLindeijer,您说得对,它是 C++17 及更高版本。一些实现在发布之前确实遵循了 C++16 语义。C++11 到 C++14 的正确处理应该是使用 `std::make_shared&lt;T&gt;`。 (2认同)

小智 187

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

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

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Run Code Online (Sandbox Code Playgroud)

这两个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;
}
Run Code Online (Sandbox Code Playgroud)

此代码与前面的示例具有相同的问题,但是以更微妙的形式.构造时,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;
}
Run Code Online (Sandbox Code Playgroud)

执行此操作时,请记住,您调用的对象shared_from_this必须由shared_ptr对象拥有.这不起作用:

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

  • 谢谢,这说明问题比目前接受的答案更好地解决了. (12认同)
  • 我很确定最后一行应该是`shared_ptr <S> sp2 = p-> not_dangerous();`因为这里的缺陷是你必须在调用`shared_from_this()之前以正常的方式创建一个shared_ptr第一次!**这很容易出错!在C++ 17之前,在通过正常方式创建一个shared_ptr之前调用`shared_from_this()`是**UB**:`auto sptr = std :: make_shared <S>();`或`shared_ptr <S > sptr(new S());`.值得庆幸的是,从C++ 17开始,这样做会抛出. (4认同)
  • +1:很好的答案。顺便说一句,代替 `shared_ptr&lt;S&gt; sp1(new S);` 可能更喜欢使用 `shared_ptr&lt;S&gt; sp1 = make_shared&lt;S&gt;();`,例如参见 http://stackoverflow.com /questions/18301511/stdshared-ptr-initialization-make-sharedfoo-vs-shared-ptrtnew-foo (2认同)
  • 不好的例子:`S * s = new S(); shared_ptr &lt;S&gt; ptr = s-&gt; not_dangerous();`&lt;-[仅允许在以前共享的对象(即在std :: shared_ptr &lt;T&gt;管理的对象上)调用shared_from_this。否则,该行为是不确定的(直到C ++ 17)(从C ++ 17起,由默认构造的weak_this的shared_ptr构造函数引发)std :: bad_weak_ptr引发。](http://en.cppreference.com/ w / cpp / memory / enable_shared_from_this)。所以现实是它应该被称为“ always_dangerous()”,因为您需要知道它是否已经被共享。 (2认同)
  • @AnorZaken好点。如果您提交了编辑请求以进行修复,这将很有用。我刚刚这样做。对于发布者,另一个有用的事情是不要选择主观的,上下文相关的方法名称! (2认同)

mac*_*nir 28

这是我的解释,从坚果和螺栓的角度来看(最高的答案并没有'点击'跟我一起).*请注意,这是调查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负责重新计数T并在他们的引用时删除它-count达到零.

  • 这是正确的,真正重要的部分是“所以,当您第一次创建...”,因为这是一个**要求**(正如您所说,在将对象指针传递给shared_ptr ctor之前,weak_ptr不会被初始化!),如果您不小心,这个要求可能会导致严重错误。如果你在调用`shared_from_this`之前没有创建shared_ptr,你会得到UB - 同样,如果你创建多个shared_ptr,你也会得到UB。你必须以某种方式确保你_exactly_创建了一个shared_ptr。 (2认同)
  • 换句话说,'enable_shared_from_this`的整个想法一开始是脆弱的,因为要点是能够从`T*`得到`shared_ptr <T>`,但实际上当你得到指针`T*t时"假设任何关于它已经被共享的东西通常是不安全的,并且做出错误的猜测就是UB. (2认同)

Nin*_*rez 7

在一种情况下,我发现 enable_shared_from_this 非常有用:使用异步回调时的线程安全

想象一下 Client 类有一个 AsynchronousPeriodicTimer 类型的成员:

struct AsynchronousPeriodicTimer
{
    // call this periodically on some thread...
    void SetCallback(std::function<void(void)> callback); 
    void ClearCallback(); // clears the callback
}

struct Client
{
    Client(std::shared_ptr< AsynchronousPeriodicTimer> timer) 
        : _timer(timer)

    {
        _timer->SetCallback(
            [this]
            () 
            {
                assert(this); // what if 'this' is already dead because ~Client() has been called?
                std::cout << ++_counter << '\n';
            }
            );
    }
    ~Client()
    {
        // clearing the callback is not in sync with the timer, and can actually occur while the callback code is running
        _timer->ClearCallback();
    }
    int _counter = 0;
    std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}

int main()
{
    auto timer = std::make_shared<AsynchronousPeriodicTimer>();
    {
        auto client = std::make_shared<Client>(timer);
        // .. some code    
        // client dies here, there is a race between the client callback and the client destructor           
    }
}
Run Code Online (Sandbox Code Playgroud)

客户端类订阅一个回调函数给周期性定时器。一旦客户端对象超出范围,回调和析构函数之间就会出现竞争条件。可以使用悬空指针调用回调!

解决方法:使用 enable_shared_from_this:

struct Client : std::enable_shared_from_this<Client>
{
Client(std::shared_ptr< AsynchronousPeriodicTimer> timer) 
    : _timer(timer)

    {

    }

    void Init()
    {
        auto captured_self = weak_from_this(); // weak_ptr to avoid cyclic references with shared_ptr

        _timer->SetCallback(
        [captured_self]
        () 
        {
            if (auto self = captured_self.lock())
            {
                // 'this' is guaranteed to be non-nullptr. we managed to promote captured_self to a shared_ptr           
                std::cout << ++self->_counter << '\n';
            }

        }
        );
    }
    ~Client()
    {
        _timer->ClearCallback();
    }
    int _counter = 0;
    std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}
Run Code Online (Sandbox Code Playgroud)

Init方法与构造函数分离,因为初始化过程enable_shared_from_this直到构造函数退出才结束。因此,额外的方法。从构造函数中订阅异步回调通常是不安全的,因为回调可能会访问未初始化的字段。