在学习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> ,其原型为:
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 。
使用智能指针的初衷就是为了方便资源管理,如果在某些地方使用智能指针,某些地方使用原始指针,很容易破坏智能指针的语义,从而产生各种错误。
答案是不能,因为这样会造成两个非共享的share_ptr 指向同一个对象,未增加引用计数导对象被析构两次(这里可能会有疑问,为什么bp2=bp1->getptr() 后,bp2 和bp1 并非共享的,我在文末会贴出StackOverflow 上的解释)。例如: 运行结果(错误,显然Bad 对象被释放了两次):
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);
}
};
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();
}
意思就是说,要是用shared_from_this() 返回一个指向this 的智能指针,必须使用一个指向该对象的shared_ptr 去接收才行。
我所遇到的使用场景
最后再说几句,可能对你有用也可能没用,只是我自己的一点总结和思考。在看muduo 网络库的时候,可以看到用户通过实现外部回调函数如连接回调onConnection 、事件发生时的消息回调onMessage ,将这些回调分别设置到TcpServer 的成员变量上,TcpServer 在创建TcpConnection 对象时,将这些用户写好的回调注册给TcpConnection ,同时TcpConnection 创建时,会将TcpConnection 自己的成员函数注册给当前accept 返回的connfd 对应的Channel 对象上,注册的回调如下:
我们可以看到,TcpConnection 将其this 指针传给了Channel 对象,所以说如果此时Channel 感兴趣的事件依然被Poller 监听着,这时候突然有事件发生,那么TcpConnection 对象若已经销毁了呢?那么Channel 对象在调用往该Channel 对象中注册了的TcpConnection 的成员函数时就会发生错误。
所以如果有可读事件发生,会调用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 专栏的时候再讨论这个问题。
参考:
- https://blog.csdn.net/qq_33113661/article/details/89019991
- https://blog.csdn.net/caoshangpa/article/details/79392878
- https://stackoverflow.com/questions/712279/what-is-the-usefulness-of-enable-shared-from-this
|