0CCh Blog

std::enable_shared_from_this原理浅析

在解释std::enable_shared_from_this之前,先看一个std::shared_ptr典型用法:

#include <memory>

int main()
{
std::shared_ptr<int> pt1{ new int{ 10 } };
auto pt2{ pt1 };
}

这时pt1pt2共用了引用计数,当pt1pt2的生命周期都结束时,new int{10}分配的内存会被释放。下面的做法会导致内存多次释放,因为它们没有使用共同的引用计数:

#include <memory>

int main()
{
auto pt{ new int{ 10 } };
std::shared_ptr<int> pt1{ pt };
std::shared_ptr<int> pt2{ pt };
}

当然,我想应该也没有人这么使用std::shared_ptr。不过下面这个错误倒是比较常见:

struct SomeData;
void SomeAPI(const std::shared_ptr<SomeData>& d) {}

struct SomeData {
void NeedCallSomeAPI() {
// 需要用this调用SomeAPI
}
};

上面这段代码需要在NeedCallSomeAPI函数中调用SomeAPI,而SomeAPI需要的是一个std::shared_ptr<SomeData>的实参。这个时候应该怎么做?

struct SomeData {
void NeedCallSomeAPI() {
SomeAPI(std::shared_ptr<SomeData>{this});
}
};

上面的做法是错误的,因为SomeAPI调用结束后std::shared_ptr<SomeData>对象的引用计数会降为0,导致this被意外释放。

这种情况下,我们需要使用std::enable_shared_from_this ,使用方法很简单,只需要让SomeData继承std::enable_shared_from_this<SomeData>,然后调用shared_from_this吗,例如:

#include <memory>

struct SomeData;
void SomeAPI(const std::shared_ptr<SomeData>& d) {}

struct SomeData:std::enable_shared_from_this<SomeData> {
static std::shared_ptr<SomeData> Create() {
return std::shared_ptr<SomeData>(new SomeData);
}
void NeedCallSomeAPI() {
SomeAPI(shared_from_this());
}
private:
SomeData() {}
};


int main()
{
auto d{ SomeData::Create() };
d->NeedCallSomeAPI();
}

std::enable_shared_from_this 的实现比较复杂,但是实现原理则比较简单。它内部使用了std::weak_ptr来帮助完成指针相关控制数据的同步,而这份数据是在创建std::shared_ptr的时候完成的。我们来重点解析这一点。

template<class T>
class enable_shared_from_this {
mutable weak_ptr<T> weak_this;
public:
shared_ptr<T> shared_from_this() {
return shared_ptr<T>(weak_this);
}
shared_ptr<const T> shared_from_this() const {
return shared_ptr<const T>(weak_this);
}
...
template <class U> friend class shared_ptr;
};

以上是摘要的enable_shared_from_this的代码,这份代码中有两个关键要素。首先weak_this被声明为mutable,这让weak_this可以在const的限定下修改,其次也是最关键的地方,该类声明了shared_ptr为友元。这意味着std::shared_ptr可以修改weak_this,并且weak_this被初始化的地方在std::shared_ptr中。进一步说,没有std::shared_ptrenable_shared_from_this是没有灵魂的:

#include <memory>

struct SomeData;
void SomeAPI(const std::shared_ptr<SomeData>& d) {}

struct SomeData:std::enable_shared_from_this<SomeData> {
void NeedCallSomeAPI() {
SomeAPI(shared_from_this());
}

};


int main()
{
auto d{ new SomeData };
d->NeedCallSomeAPI();
}

在这份代码中调用shared_from_this会出错。

再深入一步,std::shared_ptr是如何判断实例化对象类型是否继承std::enable_shared_from_this,并且通过判断结果决定是否初始化weak_this的呢?答案是SFINAE(“Substitution Failure Is Not An Error“)。

让我们查看VS2019的STL代码:

template <class _Ty>
class enable_shared_from_this {
public:
using _Esft_type = enable_shared_from_this;
...
}

template <class _Yty, class = void>
struct _Can_enable_shared : false_type {};

template <class _Yty>
struct _Can_enable_shared<_Yty, void_t<typename _Yty::_Esft_type>>
: is_convertible<remove_cv_t<_Yty>*, typename _Yty::_Esft_type*>::type {
};

这里的重点是_Can_enable_shared,如果目标类型有内嵌类型_Esft_type,并且目标类型和内嵌类型的指针是可转换的,也就是有继承关系,那么类型结果为true_type,反之为false_type

template <class _Ux>
void _Set_ptr_rep_and_enable_shared(_Ux* const _Px, _Ref_count_base* const _Rx) noexcept {
this->_Ptr = _Px;
this->_Rep = _Rx;
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));
}
}
}

接下来,如果对象不是数组、不是volatile声明的并且_Can_enable_shared返回true_type,那么_Wptr才会被初始化。std::shared_ptr的构造函数以及std::make_shared函数都会调用该函数。

以上就是std::enable_shared_from_this实现原理中比较关键的一个部分。