C++ enable_shared_from_this解析

当一个类的成员函数里需要把当前类对象作为参数传给外部时(例如传递给异步线程),如何安全的的传递和释放将是一个棘手的问题,而C++11的enable_shared_from_this就是为此而设计的,本文就和大家一起来解析下。

一、概述

1、什么是enable_shared_from_this?

enable_shared_from_this是C++11开始支持的一个新特性,它是一个模板类,定义在头文件 <memory>,其原型为:

template< class T > 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::shared_from_this 成员函数,将会返回一个新的 std::shared_ptr 对象,它与 pt 共享 t 的所有权。

2、什么情况下需要用 enable_shared_from_this?

当一个类被共享智能指针 share_ptr 管理,且在类的成员函数里需要把当前类对象作为参数传给其他函数时(例如传递给异步线程),这时就需要传递一个指向自身的 share_ptr。

在这种情况下,要在一个类的成员函数中,对外部返回 this 指针就成了一个很棘手的问题。

3、如何安全的将 this 指针返回给调用者?

一般来说,我们不能直接将 this 指针返回。如果函数将 this 指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变量并不知道,此时如果外部变量再使用这个指针,就会使得程序崩溃。例如下边这种场景:

 struct Bad
 {
     void fun()
     {
         shared_ptr<Bad> sp{ this };
         cout << sp->count() << endl;
     }
 };
 shared_ptr<Bad> sp{make_shared<Bad>()};
 sp->fun(); //输出为1

在 func 函数构造智能指针时,我们无法确定这个对象是不是被 shared_ptr 管理着,因此这样构造的 shared_ptr 并不是与其他 shared_ptr 共享一个计数器,那么,在析构时就会导致对象被重复释放,从而引发错误。

在一个对象内部构造该对象的 shared_ptr 时,即使该对象已经被 shared_ptr 管理着,也不会造成对象被两个独立的智能指针管理。

这就要求我们在对象内构造对象的智能指针时,必须能识别有对象是否已经由其他智能指针管理、智能指针的数量,并且我们创建智能指针后也能让之前的智能指针感知到。

正确做法是继承 enable_shared_from_this 类,调用 shared_from_this() 函数生成 shared_ptr,使用如下:

 struct Good : public std::enable_shared_from_this<Good>
 {
   void fun()
   {
     shared_ptr<Good> sp{ shared_from_this() };
     cout << sp->count() << endl;
   }
 };
 shared_ptr<Good> sp{make_shared<Good>()}; // *1*
 sp->fun(); //输出为2

二、实现原理

1、enable_shared_from_this的标准库实现

gcc是通过 weak_ptr 来实现的。先用要管理对象(obj)的指针和已有的管理obj的shared_ptr(sp1,…,spn) 的个数(spi->use_count())来初始化一个 weak_ptr<Obj>(&obj , spi->use_count()),然后用这个 weak_ptr 构造一个 shared_ptr。

 // enable_shared_from_this的实现
 // 基于(/usr/include/c++/7.3.0/bits/shared_ptr.h)
 // 此代码是对gcc实现的简化版本, 仅作为描述原理用.
 template<typename T>
 class enable_shared_from_this
 {
 public:
     shared_ptr<T> shared_from_this()
     {
         return shared_ptr<T>(this->weak_this);
     }
     shared_ptr<const T> shared_from_this() const
     {
         return shared_ptr<const T>(this->weak_this);
     }
 private:
     template<typename>
     friend class shared_ptr;
 
     template<typename T1>
     void _M_weak_assign(T1* p, const shared_count<>& n)
     {
       weak_this._M_assign(p, n);
     }
 
     mutable weak_ptr<T> weak_this;
 };

enable_shared_from_this<T> 类中定义了一个 weak_ptr<T>,起到了上文提到的从obj指针生成 shared_ptr<T> 对象的作用。按照先前的原理,我们可能认为是在obj初始化的时候,同时对 weak_this 进行初始化,但是在这段代码里显然没有对 weak_this 进行任何初始化工作(原始代码里也没有,gcc为什么不这样实现呢?这是因为当对象没有由智能指针管理时,这些操作是没有必要的。 所以应该把这个任务交给 shared_ptr)。

gcc在 shared_ptr<T> 的构造函数中对 weak_ptr<T> 进行处理。从 Good 类来看,就是在 *1* 处对 Good 对象中的 weak_ptr<Good> weak_this 进行处理,使其指向一个有效的 Good 对象,并修改 use_count。上面 Good 类对 enable_shared_from_this 的使用是少数几种有效的方法,必须保证,如果对一个对象调用 shared_from_this(),该对象必须是由 shared_ptr<T> 持有的。从上一段的原理中可以理解这样做的原因:第一个持有 Good 对象 obj 的 shared_ptr<T> sp1 会对 obj 的 weak_this 进行处理,以使其有效。

2、shared_ptr 构造函数中的特殊处理

在 shared_ptr 中定义了这样一个函数(来自/usr/include/c++/7.3.0/bits/shared_ptr_base.h中类__shared_ptr):

 template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
 typename enable_if<__has_esft_base<_Yp2>::value>::type
 _M_enable_shared_from_this_with(_Yp* __p) noexcept
 {
     if(auto __base = __enable_shared_from_this_base(_M_refcount, __p))
         __base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
 }
 
 template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
 typename enable_if<!__has_esft_base<_Yp2>::value>::type
 _M_enable_shared_from_this_with(_Yp*) noexcept { }

其中 _Yp 是 shared_ptr 管理的对象的类型。

这两个模板函数表示:当 _Yp 是 enable_shared_from_this 的子类时,就会生成第一个函数,其功能是通过 _Yp 对象的指针来调用其 _M_weak_assign 函数以修改 _Yp 对象的 weak_this 成员,而实际上 _M_weak_assign 调用的是 weak_this._M_assign 函数。

 // from shared_ptr_base.h class __weak_ptr, derived by weak_ptr
 void _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount) noexcept
 {
     if (use_count() == 0)
     {
         _M_ptr = __ptr;
         _M_refcount = __refcount;
     }
 }

三、实例

下边用实际的例子来说明下如何安全的向外传递类对象 this 指针:

#include <iostream>
#include <stdlib.h>
#include <memory>
using namespace std;

// 推荐写法
struct Good : std::enable_shared_from_this<Good>
{
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

// 错误用法:用不安全的表达式试图获得 this 的 shared_ptr 对象
struct Bad
{
    std::shared_ptr<Bad> getptr() {
        return std::shared_ptr<Bad>(this);
    }
    ~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
 

int main()
{
    // 1.正确用法: 两个 shared_ptr 共享同一个对象
    std::shared_ptr<Good> gp1 = std::make_shared<Good>();
    std::shared_ptr<Good> gp2 = gp1->getptr();
    std::cout << "gp2.use_count() = " << gp2.use_count() << std::endl;    // 2
 
    // 2.错误用法: 调用 shared_from_this, 但其没有被 std::shared_ptr 占有 
    try {
        Good not_so_good;
        std::shared_ptr<Good> gp1 = not_so_good.getptr();
    } catch(std::bad_weak_ptr& e) { // 在 C++17 之前,编译器不能捕获 enable_shared_from_this 抛出的std::bad_weak_ptr 异常
        std::cout << e.what() << std::endl;    
    }
 
    // 3.错误用法,每个 shared_ptr 都认为自己是对象的唯一拥有者
    // 调用错误的用法,会导致两次析构Bad的对象,第二次析构时,指针指向的空间已经被析构,会导致程序出错
    std::shared_ptr<Bad> bp1 = std::make_shared<Bad>();
    std::shared_ptr<Bad> bp2 = bp1->getptr();
    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;    // 1
    std::cout << "bp2.use_count() = " << bp2.use_count() << std::endl;    // 1

    return 0;
}

# 控制台输出
gp2.use_count() = 2
bad_weak_ptr
bp1.use_count() = 1
bp2.use_count() = 1
Bad::~Bad() called

ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x60300002ab30 in thread T0
AddressSanitizer: bad-free

四、总结

enable_shared_from_this 的常见实现为:其内部保存着一个对 this 的弱引用(例如 std::weak_ptr )。 std::shared_ptr 的构造函数检测无歧义且可访问的enable_shared_from_this 基类,并且若内部存储的弱引用没有被已存在的 std::shared_ptr 占有,则赋值新建的 std::shared_ptr 为内部存储的弱引用。为另一个 std::shared_ptr 所管理的对象构造一个 std::shared_ptr ,将不会考虑内部存储的弱引用,从而将导致未定义行为(undefined behavior)。

只允许在已被std::shared_ptr 管理的对象上调用 shared_from_this 。否则调用行为抛出 std::bad_weak_ptr 异常(通过 shared_ptr默认构造的 weak_this 的构造函数)。

enable_shared_from_this 提供了更安全的方案,以替代 std::shared_ptr(this) 这样的表达式(这种不安全的表达式可能会导致 this 被多个互不知晓的所有者析构)。当涉及需要在类的成员函数里把当前类对象作为参数传给外部时,建议其基类继承std::enable_shared_from_this<T>,并使用shared_from_this(),以降低安全风险。

 

yan 23.3.22 23.58

参考:

std::enable_shared_from_this

enable_shared_from_this类的作用和实现

C++11中enable_shared_from_this的用法解析

欢迎关注下方“非著名资深码农“公众号进行交流~

发表评论

邮箱地址不会被公开。