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:  //在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;  //const函数也可以修改该变量

      //  _M_weak_assign函数在类型T被包覆在shared_ptr的过程中会被调用
        template<typename _Tp1>
        void _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept
	{ _M_weak_this._M_assign(__p, __n); }
    };

      // 其中_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这个会在智能指针构造时会被调用的函数,是为了为从enable_shared_from_this中继承得来的_M_weak_this赋地址!
     
       

通过上面的分析,可以得出一个结论,这样的代码是错误的:

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

原因在于没有把obj交给shared_ptr管理起来而是直接使用了原生指针
所以导致了 _M_weak_this没有初始化!
进而在调用shared_from_this()时,把一个空weak_ptr转换为shared_ptr导致了异常。
同理,也不能将继承了enable_shared_from_this的对象创建在栈上。
这是STL在提醒我们,不要破坏智能指针的语义!

shared_from_this()不能在构造函数中被调用!

原理分析

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

posted @ 2019-08-15 15:46  joeyzzz  阅读(1649)  评论(0编辑  收藏  举报