392

我在阅读 Boost.Asio 示例时遇到了enable_shared_from_this问题,在阅读了文档后,我仍然不知道应该如何正确使用它。有人可以给我一个例子和解释什么时候使用这个类是有意义的。

4

6 回答 6

394

它使您能够获得一个有效的shared_ptr实例this,当您拥有的是this. 没有它,您将无法获得shared_ptrto this,除非您已经拥有一个成员。此示例来自enable_shared_from_this 的 boost 文档

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()返回一个 valid 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 标准的一部分。您也可以从那里以及从 boost 中获取它。

于 2009-04-03T02:00:52.390 回答
240

来自 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
}
于 2011-04-05T07:00:06.777 回答
34

这是我的解释,从具体细节的角度来看(最佳答案并没有和我一起“点击”)。*请注意,这是调查 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* 上创建 a 时,T* 的内部 weak_ptr 将使用 1 的引用计数进行初始化。新的shared_ptr基本上回到了 this 上weak_ptr

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

于 2012-08-01T12:34:07.933 回答
11

有一种特殊情况我觉得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           
    }
}

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

解决方案:使用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()
    {
        // the destructor cannot be called while the callback is running. shared_ptr guarantees this
        _timer->ClearCallback();
    
    }
    int _counter = 0;
    std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}

的机制enable_shared_from_this,结合std::shared_ptr引用计数固有的线程安全性,使我们能够确保Client在回调代码访问其内部成员时不会破坏对象。

请注意,该Init方法与构造函数是分开的,因为在enable_shared_from_this构造函数退出之前,初始化过程不会完成。因此,额外的方法。从构造函数中订阅异步回调通常是不安全的,因为回调可能会访问未初始化的字段。

于 2020-08-25T13:19:01.017 回答
4

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

于 2012-06-13T13:25:40.747 回答
4

在 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;
};           
于 2017-11-15T21:58:48.943 回答