在学习muduo
时,看到C++11的特性,在TcpConnection
类的声明中,继承了enable_shared_from_this
这个模板类,部分代码片段如下:
class TcpConnection : noncopyable,
public std::enable_shared_from_this<TcpConnection>
{...}
于是我在想,enable_shared_from_this
是干什么的呢?enable_shared_from_this
是一个模板类,定义于头文件<memory>
,其原型为:
/**
* @brief Base class allowing use of member function shared_from_this.
*/
template<typename _Tp>
class enable_shared_from_this
{..}
std::enable_shared_from_this
能让一个对象(假设其名为t
,且已被一个std::shared_ptr
对象pt
管理)安全地生成其他额外的std::shared_ptr
实例(假设名为pt1
,pt2
,…),它们与pt
共享对象t
的所有权。若一个类T
继承std::enable_shared_from_this<T>
,则会为该类T
提供成员函数:shared_from_this()
。当T
类型对象t
被一个为名为pt
的std::shared_ptr<T>
类对象管理时,调用T::shared_from_this
成员函数,将会返回一个新的std::shared_ptr<T>
对象,它与pt
共享t
的所有权。
当类A
被share_ptr<A>
管理,且在类A
的成员函数里需要把当前类对象this
作为参数传给其他函数时,就需要传递一个指向自身的share_ptr
。
this
指针?使用智能指针的初衷就是为了方便资源管理,如果在某些地方使用智能指针,某些地方使用原始指针,很容易破坏智能指针的语义,从而产生各种错误。
share_ptr<this>
吗?答案是不能,因为这样会造成两个非共享的share_ptr
指向同一个对象,未增加引用计数导对象被析构两次(这里可能会有疑问,为什么bp2=bp1->getptr()
后,bp2
和bp1
并非共享的,我在文末会贴出StackOverflow
上的解释)。例如:
bp1.use_count() = 1
bp2.use_count() = 1
Bad::~Bad() called
Bad::~Bad() called
关于上面遗留的问题,即为什么bp2=bp1->getptr()
后,bp2
和bp1
并非共享的,我直接贴出来自Stackoverflow
的回答,很清晰地讲解了enable_shared_from_this
所解决的用其他方法无法解决的问题。
…code like this won’t work correctly:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Neither of the two shared_ptr
objects knows about the other, so both will try to release the resource when they are destroyed. That usually leads to problems.
Similarly, if a member function needs a shared_ptr
object that owns the object that it’s being called on, it can’t just create an object on the fly:
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;
}
This code has the same problem as the earlier example, although in a more subtle form. When it is constructed, the shared_ptr
object sp1
owns the newly allocated resource. The code inside the member function S::dangerous
doesn’t know about that shared_ptr
object, so the shared_ptr
object that it returns is distinct from sp1
. Copying the new shared_ptr
object to sp2
doesn’t help; when sp2
goes out of scope, it will release the resource, and when sp1
goes out of scope, it will release the resource again.
意思就是说,因为S::dangerous()
成员函数只知道要去返回一个智能指针,但并不知道外部会有一个智能指针sp1
间接调用了它,因此返回的智能指针shared_ptr<S>(this)
和sp1
这个智能指针是有区别的,它们两个不同,因此sp2
指向了这个新的智能指针对应的this
,sp1
和sp2
的引用计数均为1
。
这个解释听起来好像很勉强,但是也有点道理,暂且记住这种写法是错误的就行。接下来给出了正确的解决方法,即使用enable_shared_from_this<>
。
The way to avoid this problem is to use the class template enable_shared_from_this
. The template takes one template type argument, which is the name of the class that defines the managed resource. That class must, in turn, be derived publicly from the template; like 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;
}
When you do this, keep in mind that the object on which you call shared_from_this
must be owned by a shared_ptr
object. This won’t work:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
意思就是说,要是用shared_from_this()
返回一个指向this
的智能指针,必须使用一个指向该对象的shared_ptr
去接收才行。
最后再说几句,可能对你有用也可能没用,只是我自己的一点总结和思考。在看muduo
网络库的时候,可以看到用户通过实现外部回调函数如连接回调onConnection
、事件发生时的消息回调onMessage
,将这些回调分别设置到TcpServer
的成员变量上,TcpServer
在创建TcpConnection
对象时,将这些用户写好的回调注册给TcpConnection
,同时TcpConnection
创建时,会将TcpConnection
自己的成员函数注册给当前accept
返回的connfd
对应的Channel
对象上,注册的回调如下:
所以如果有可读事件发生,会调用TcpConnection.cc
的成员方法,即下图的363
行调用handleRead
回调函数,然后在下面370
行的代码处,如果直接传入this
指针,那么如果该TcpConnection
对象已经被析构了(当然muduo
使用了tie
和enable_shared_from_this
的方式延续了TcpConnection
对象的生命周期,这个暂时不去讨论,就假定对象提前被析构了),这个时候就会出现问题。但是如果传入一个shared_ptr
对象,让引用计数加一,这样对象就不会因为已经销毁而导致程序报错,那么我们该如何获取一个shared_ptr
对象呢?
做法就是:让TcpConnection
继承自enable_shared_from_this<TcpConnection>
类,然后在下图代码370
行处传入shared_from_this
,即传入了一个引用了TcpConnection
对象的shared_ptr
智能指针。(muduo
使用shared_from_this()
主要还是为了控制TcpConnection
对象的生命周期)
关于muduo
的思考,总结下来就是:
一般是用在对象的成员函数里面使用的时候,比方说你在成员函数里设置了一个回调函数,就需要传递这个对象指针到回调函数,这样回调函数被调用时,可以通过这个对象指针获取对象的上下文信息。如果是直接传this
,那么有可能回调函数被调用的时候,这个对象已经被析构了,这个时候就有问题了。所以需要传一个shared_ptr
对象,让引用计数加一,这样对象就不会销毁。 那么这种情况下,你就没法直接获取一个share_ptr
对象,所以就用到了enable_shared_from_this
,继承该类并调用该类的成员函数shared_from_this()
即可。
当然对于上面的讨论,即TcpConnection
如果在Channel
对象调用回调函数之前已经析构的这种情况其实并不会在muduo
库中发生,之所以举这个例子只是为了讨论一种可能会使用shared_from_this()
的场景,而且muduo
中使用shared_from_this()
传递到这些事件回调中,目的是控制TcpConnection
对象的引用计数,同时muduo
配合使用了weak_ptr
,巧妙延长了TcpConnection
的生命周期,到时候写muduo
专栏的时候再讨论这个问题。
参考: