C++11标准库的一个工具类enable_shared_from_this<T>的作用及原理分析

介绍

enable_shared_from_this类位于标准库的中,当一个类T公有继承它,如

class T : public enable_shared_from_this<T> {
};

类T会继承到一个公有方法

shared_ptr<T>  shared_from_this();

要在T类中使用该函数,是继承enable_shared_from_this类的唯一目标。

要解决的问题

如果一个程序中,对象内存的生命周期的全部由智能指针来管理。在这种情况下,要在一个类的成员函数中,对外部返回this指针成了一个很棘手的问题。
具体代码如下:

class obj {
public:

    obj* getRawThis() {
        return this;  // 这样做语法上没有问题,但是破坏了智能指针的语义了,使用了原生指针
    }

    shared_ptr<obj> getSharedCopy() {
        return shared_ptr<obj>(this); // 这样做程序会崩掉,这个临时变量在函数完毕后会被析构掉,会执行delete this,
    }

    shared_ptr<obj>& getSharedRef() {
        if(p == nullptr) {
            p = new shared_ptr<obj>(this);
        }
        return *p;   // 这样做就更憨了,obj对象本身持有了包覆自己this指针的shared_ptr,那么他们的生命周期是“同步”的,这样被管理的obj永远不会释放
    }

    // 当然,从上面也可以看出,实际上核心的问题,就是:
    // 1. 如果采用返回拷贝,那么临时变量会把this析构掉
    // 2. 如果采用返回引用,那么obj对象本身必须持有一个管理他的shared_ptr,这样会导致obj永远不会被释放
    // 可用的解决方案有以下两种


    void getSharedFromParam(shared_ptr<obj>& p) {
        p.reset(this);    // 通过函数参数来实现规避掉拷贝语义,但是这样太过于丑陋
    }

    shared_ptr<obj> getSharedFromThis() {
        return shared_from_this();  // 这是C++11推荐的方法,可以实现安全的返回一个shared_ptr<this>,返回到外面的shared_ptr的拷贝的use_count是1,不用担心this被意外delete和引用计数异常的问题。
    }
private:
    shared_ptr<obj> *p;
};

一个坑(以GNU版本STL为例)

先看看enable_shared_from_this的源码,在shared_ptr.h中(已经省略了无关部分)

template<typename _Tp>
    class enable_shared_from_this
    {
    protected:  //这里把构造函数设置为protected是为了在public继承后,他们仍对子类可见
      enable_shared_from_this() noexcept { }
      ~enable_shared_from_this() { }

    //...

    public:
      shared_ptr<_Tp>
      shared_from_this()
      { return shared_ptr<_Tp>(this->_M_weak_this); }
    
    //...

    private:
      mutable weak_ptr<_Tp>  _M_weak_this;  //用来保存内存地址的weak_ptr

      //  _M_weak_assign函数在作为enable_shared_from_this<obj>的子类的obj被包覆在shared_ptr的过程中会被调用
      // 其中_M_assign()是weak_ptr的父类,__weak_ptr的成员函数
      // _M_assign()的源码如下
      /*
      void  _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount) noexcept
      {
    if (use_count() == 0)
      {
        _M_ptr = __ptr;
        _M_refcount = __refcount;
      }
      }
      可以看出, _M_assign()的作用是为只能指针赋地址和引用计数(当然引用计数对weak_ptr没什么意义)
      所以,_M_weak_assign这个会在智能指针构造时会被调用的函数,是为了给obj从enable_shared_from_this中继承得来的_M_weak_this赋地址!
     */
        template<typename _Tp1>
        void _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept
    { _M_weak_this._M_assign(__p, __n); }
    };

通过上面的分析,可以得出一个结论,这样的代码是完全不OK的:

    auto p = new obj();
    p->getSharedFromThis()->doSomething(); // 这里会报一个异常:bad_weak_ptr,坑

不OK的原因就是没有把obj交给shared_ptr管理起来而是直接使用了原生指针,所以导致了 _M_weak_this没有初始化,进而在调用shared_from_this()时,把一个空weak_ptr转换为shared_ptr导致了异常。
这就是坑所在之处!但是这也是STL在提醒我们,不要破坏智能指针的语义!

原理分析

如果捋清楚了“坑”,那么原理就很好解释了。一句话可以概括:
enable_shared_from_this类中使用了一个私有weak_ptr来保存地址,在shared_from_this()通过把这个weak_ptr转换为引用计数为1的shared_ptr返回,避免了临时shared_ptr析构导致this被delete。

转载于:https://www.cnblogs.com/jo3yzhu/p/11358400.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值