' enable_shared_from_this '有什么用处?

我在阅读Boost时碰到了enable_shared_from_this。Asio示例和阅读文档后,我仍然不知道如何正确使用这一点。有人能给我一个例子和解释什么时候使用这个类是有意义的。

119568 次浏览

它使你能够获得一个有效的shared_ptr实例到this,当你只有this时。如果没有它,你将无法将shared_ptr转换为this,除非你已经有了一个成员。下面的例子来自提升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标准的一部分。你也可以从那里和从boost得到它。

来自Dr 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
}
注意,使用boost::intrusive_ptr不会遇到这个问题。 这通常是一个更方便的方法来解决这个问题

以下是我的解释,从具体的角度来看(上面的答案对我来说不“合适”)。*注意,这是调查Visual Studio 2012附带的shared_ptr和enable_shared_from_this的源代码的结果。也许其他编译器实现enable_shared_from_this的方式不同…*

enable_shared_from_this<T>将一个私有的weak_ptr<T>实例添加到T,该实例保存了T实例的'一个真实的引用计数'。

因此,当你第一次在一个新的T*上创建shared_ptr<T>时,这个T*的内部weak_ptr会初始化为1的refcount。新的shared_ptr基本上回到了这个weak_ptr

然后,T可以在其方法中调用shared_from_this来获得shared_ptr<T>的一个实例,该实例返回到相同的内部存储引用计数。这样,你总是有一个地方存储T*的引用计数,而不是有多个彼此不知道的shared_ptr实例,每个实例都认为自己是shared_ptr,负责对T进行引用计数,并在它们的引用计数为零时删除它。

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

在一个特殊的情况下,我发现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的初始化过程直到构造函数退出才结束。因此有了额外的方法。从构造函数内部订阅异步回调通常是不安全的,因为回调可能访问未初始化的字段。