`enable_shared_from_this`的用处是什么?

时间:2009-04-03 01:46:07

标签: c++ boost boost-asio tr1

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

7 个答案:

答案 0 :(得分:323)

当您拥有shared_ptr时,它可以让您有效this个实例到this。没有它,你将无法获得shared_ptrthis,除非你已经有一个成员。这个例子来自boost documentation for enable_shared_from_this

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标准的一部分。你也可以从那里以及从增强中获得它。

答案 1 :(得分:179)

来自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_pt r对象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
}

答案 2 :(得分:27)

这是我的解释,从一个具体的角度来看(最高的答案并没有'点击'跟我一起)。 *请注意,这是调查Visual Studio 2012附带的shared_ptr和enable_shared_from_this的源代码的结果。也许其他编译器以不同的方式实现enable_shared_from_this ... *

enable_shared_from_this<T>weak_ptr<T>添加一个私有T个实例,其中包含T实例的“一个真正的引用次数”。 / p>

因此,当您首次在新T *上创建shared_ptr<T>时,T *的内部weak_ptr会以refcount为1进行初始化。新的shared_ptr基本上会回到此{{1} }}

然后,

weak_ptr可以在其方法中调用T来获取shared_from_this的实例,返回到相同的内部存储引用计数。这样,您总是有一个存储shared_ptr<T>引用计数的地方而不是多个T*实例彼此不了解,并且每个实例都认为它们是{{1}负责重新计数shared_ptr并在其引用计数到零时删除它。

答案 3 :(得分:3)

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

答案 4 :(得分:2)

在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;
};           

答案 5 :(得分:1)

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

想象一下,客户类有一个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)

{
    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 guranteed to be non-nullptr. we managed to promote captured_self to shared_ptr           
                std::cout << ++self->_counter << '\n';
            }

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

答案 6 :(得分:-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对象并销毁临时变量。因此我的回答是不正确的。