学习muduo时对enable_shared_from_this的思考

在学习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被一个为名为ptstd::shared_ptr<T>类对象管理时,调用T::shared_from_this成员函数,将会返回一个新的std::shared_ptr<T>对象,它与pt共享t的所有权。

使用场合

当类Ashare_ptr<A>管理,且在类A的成员函数里需要把当前类对象this作为参数传给其他函数时,就需要传递一个指向自身的share_ptr

  • 为何不直接传递this指针?

使用智能指针的初衷就是为了方便资源管理,如果在某些地方使用智能指针,某些地方使用原始指针,很容易破坏智能指针的语义,从而产生各种错误。

  • 可以直接传递share_ptr<this>吗?

答案是不能,因为这样会造成两个非共享的share_ptr指向同一个对象,未增加引用计数导对象被析构两次(这里可能会有疑问,为什么bp2=bp1->getptr()后,bp2bp1并非共享的,我在文末会贴出StackOverflow上的解释)。例如:
在这里插入图片描述
运行结果(错误,显然Bad对象被释放了两次):

bp1.use_count() = 1
bp2.use_count() = 1
Bad::~Bad() called
Bad::~Bad() called

正确实现方法

在这里插入图片描述

关于上面遗留的问题,即为什么bp2=bp1->getptr()后,bp2bp1并非共享的,我直接贴出来自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指向了这个新的智能指针对应的thissp1sp2的引用计数均为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将其this指针传给了Channel对象,所以说如果此时Channel感兴趣的事件依然被Poller监听着,这时候突然有事件发生,那么TcpConnection对象若已经销毁了呢?那么Channel对象在调用往该Channel对象中注册了的TcpConnection的成员函数时就会发生错误。

所以如果有可读事件发生,会调用TcpConnection.cc的成员方法,即下图的363行调用handleRead回调函数,然后在下面370行的代码处,如果直接传入this指针,那么如果该TcpConnection对象已经被析构了(当然muduo使用了tieenable_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专栏的时候再讨论这个问题。

参考:

  1. https://blog.csdn.net/qq_33113661/article/details/89019991
  2. https://blog.csdn.net/caoshangpa/article/details/79392878
  3. https://stackoverflow.com/questions/712279/what-is-the-usefulness-of-enable-shared-from-this
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值