最近使用C++的智能指针的时候遇到了一个坑,在此记录一下。

shared_from_this用法

使用std::shared_ptr创建的对象,可以在对象内使用构造一个指向自身的shared_ptr。条件是:

  1. 类自身需要继承 std::enable_shared_from_this<T>
  2. 对象需要由shared_ptr管理,可以用std::make_shared直接创建,也可以通过shared_ptr的构造函数来创建。

满足以上两个条件,即可在类的非构造函数中使用 shared_from_this获得一个指向自身对象的共享指针。那么如果不满足以上条件会怎么样呢?

  1. 不满足条件1,编译直接报错。
  2. 不满足条件2,比如在一个普通的new出来的对象、或者栈上的对象内调用shared_from_this,会在运行时抛异常。

两种报错机制都可以让开发者在误用的时候较快的发现问题,所以用起来还是比较放心。

weak_from_this的坑

现实中,我们使用shared_from_this的时候,经常是为了得到一个weak_ptr,比如传递一个弱引用给到另外的线程,这样用来确保不会因为生命周期的问题导致空悬引用。而shared_ptr又没有一个方法用来产生一个weak_ptr,必须通过weak_ptr的构造函数来获得,算是有点麻烦。

因此在C++17中,enable_shared_from_this类新增了一个weak_from_this方法,可以一步获得一个弱引用。

但是在实际使用中,发现使用weak_from_this时,如果不满足条件2,即 在没有使用shared_ptr管理的对象中使用weak_from_this,不会有任何异常,仅仅是在lock()的时候永远只能得到一个空指针。

这就比较危险了,误用的时候一直到最终的使用阶段才能发现问题。所以建议大家慎用weak_from_this函数。

原理

其实也简单,enable_shared_from_this内部放置了一个weak_ptr成员,并且将shared_ptr设置为friend,而shared_ptr在初始化时即会判断(编译期判断):如果要构造的类是enable_shared_from_this的派生类,则去将其中的weak_ptr成员设置好。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <class _Ux>
void _Set_ptr_rep_and_enable_shared(_Ux* const _Px, _Ref_count_base* const _Rx) noexcept { // take ownership of _Px
    this->_Ptr = _Px;
    this->_Rep = _Rx;
    // 这里的_Can_enable_shared就是用来判断是否是enable_shared_from_this的派生类的
    if constexpr (conjunction_v<negation<is_array<_Ty>>, negation<is_volatile<_Ux>>, _Can_enable_shared<_Ux>>) {
        if (_Px && _Px->_Wptr.expired()) {
            _Px->_Wptr = shared_ptr<remove_cv_t<_Ux>>(*this, const_cast<remove_cv_t<_Ux>*>(_Px));
        }
    }
}

shared_from_this将该成员传递给shared_ptr构造,如果为空其构造函数会直接抛异常,weak_from_this却是直接返回了该成员本身,因此无论如何都不会抛异常。

1
2
3
4
5
6
7
shared_ptr<_Ty> shared_from_this() {
    return shared_ptr<_Ty>(_Wptr);
}

weak_ptr<_Ty> weak_from_this() noexcept {
    return _Wptr;
}

事实上这种做法我觉得是欠妥的,两个明显是对等的函数,行为却不一致。

总之后续在使用stl函数时,有条件的话还是要看一看源码。