本文深入shared_ptr,weak_ptr以及unique_ptr的MSVC源码实现,理解智能指针的具体实现
2024/8/30 补充一个 UML 类图,对于智能指针整体框架应该可以更清晰
shared_ptr
首先在MSVC里shared_ptr
继承自_Ptr_base
_EXPORT_STD template <class _Ty>
class shared_ptr : public _Ptr_base<_Ty>
默认构造函数和传入nullptr的构造函数也没做什么
constexpr shared_ptr() noexcept = default;
constexpr shared_ptr(nullptr_t) noexcept {} // construct empty shared_ptr
主要看传入非空指针的构造函数
template <class _Ux,
enable_if_t<conjunction_v<conditional_t<is_array_v<_Ty>, _Can_array_delete<_Ux>, _Can_scalar_delete<_Ux>>,
_SP_convertible<_Ux, _Ty>>,
int> = 0>
explicit shared_ptr(_Ux* _Px) { // construct shared_ptr object that owns _Px
if constexpr (is_array_v<_Ty>) {
_Setpd(_Px, default_delete<_Ux[]>{});
} else {
_Temporary_owner<_Ux> _Owner(_Px);
_Set_ptr_rep_and_enable_shared(_Owner._Ptr, new _Ref_count<_Ux>(_Owner._Ptr));
_Owner._Ptr = nullptr;
}
}
首先是条件编译,总结就是指针是可delete且_Ux
可转换为_Ty
,才实例化该模板:
-
enable_if_t<...>=0
是一个用于SFINAE(替换失败并不是一个错误)的工具。它用于根据某些编译时逻辑有条件地启用或禁用模板实例化。如果enable_if_t
中的条件为真,则启用此模板;否则,他将从候选重载集合中移除。 -
conjunction_v<...>
执行逻辑与操作,都为真才为真 -
conditional_t<...>
是一个编译时的if
语句,它根据作为第一个参数指定的条件,在第二个或第三个模板参数之间进行选择。
explicit
表示其是显式的构造函数
if constexpr
也是用于条件编译,如果_Ty
是一个数组,那么编译上面的代码,否则编译下面的代码。
这里我们关注于不是数组的情况,_Temporary_owner
是临时保存原始指针,具体就是封装了一层
template <class _Ux>
struct _Temporary_owner {
_Ux* _Ptr;
explicit _Temporary_owner(_Ux* const _Ptr_) noexcept : _Ptr(_Ptr_) {}
_Temporary_owner(const _Temporary_owner&) = delete;
_Temporary_owner& operator=(const _Temporary_owner&) = delete;
~_Temporary_owner() {
delete _Ptr;
}
};
_Set_ptr_rep_and_enable_shared
首先看一下 new _Ref_count<_Ux>(_Owner._Ptr)
template <class _Ty>
class _Ref_count : public _Ref_count_base { // handle reference counting for pointer without deleter
public:
explicit _Ref_count(_Ty* _Px) : _Ref_count_base(), _Ptr(_Px) {}
private:
void _Destroy() noexcept override { // destroy managed resource
delete _Ptr;
}
void _Delete_this() noexcept override { // destroy self
delete this;
}
_Ty* _Ptr;
};
_Ref_count
继承自 _Ref_count_base
其持有原始指针的拷贝,以及重写了 _Destroy
和 _Delete_this
进一步看下 _Ref_count_base
,两个私有变量,初始都是 1,一个用于使用计数,一个用于weak_ptr计数
_Atomic_counter_t _Uses = 1;
_Atomic_counter_t _Weaks = 1;
接下来继续看下 _Set_ptr_rep_and_enable_shared
,主要就是把指针和引用计数赋给当前 shared_ptr
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;
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_ptr(const shared_ptr& _Other) noexcept { // construct shared_ptr object that owns same resource as _Other
this->_Copy_construct_from(_Other);
}
_Copy_construct_from
是基类 _Ptr_base
的方法,顺便贴下移动构造函数
template <class _Ty2>
void _Move_construct_from(_Ptr_base<_Ty2>&& _Right) noexcept {
// implement shared_ptr's (converting) move ctor and weak_ptr's move ctor
_Ptr = _Right._Ptr;
_Rep = _Right._Rep;
_Right._Ptr = nullptr;
_Right._Rep = nullptr;
}
template <class _Ty2>
void _Copy_construct_from(const shared_ptr<_Ty2>& _Other) noexcept {
// implement shared_ptr's (converting) copy ctor
_Other._Incref();
_Ptr = _Other._Ptr;
_Rep = _Other._Rep;
}
看下 _Other._Incref
,就是调用计数模块的 _Incref
方法
void _Incref() const noexcept {
if (_Rep) {
_Rep->_Incref();
}
}
_MT_INCR
是宏定义,展开后可以看出是内部加锁的递增,保证多线程下的安全性
void _Incref() noexcept { // increment use count
_MT_INCR(_Uses);
// 展开宏定义后
// _InterlockedIncrement(reinterpret_cast<volatile long*>(&_Uses));
}
最后看下析构函数,调用基类的 _Decref
方法
~shared_ptr() noexcept { // release resource
this->_Decref();
}
基类的 _Decref
方法就是调用引用对象的_Decref
方法
void _Decref() noexcept { // decrement reference count
if (_Rep) {
_Rep->_Decref();
}
}
引用对象的_Decref
方法就是一个内部加锁的减减操作,如果减完是0的话,释放掉资源
void _Decref() noexcept { // decrement use count
if (_MT_DECR(_Uses) == 0) {
_Destroy();
_Decwref();
}
}
_Decwref
减少weak引用计数,如果等于0了,_Delete_this
把 Ref_count
对象释放掉,上面的_Destroy
是吧持有的原始指针对象释放掉
void _Decwref() noexcept { // decrement weak reference count
if (_MT_DECR(_Weaks) == 0) {
_Delete_this();
}
}
我们也可以自定义一个 deletor
传入,得是一个可调用对象
template <class _Ux, class _Dx,
enable_if_t<conjunction_v<is_move_constructible<_Dx>, _Can_call_function_object<_Dx&, _Ux*&>,
_SP_convertible<_Ux, _Ty>>,
int> = 0>
shared_ptr(_Ux* _Px, _Dx _Dt) { // construct with _Px, deleter
_Setpd(_Px, _STD move(_Dt));
}
_Setpd
里new了一个 _Ref_count_resource
其和 _Ref_count
都是继承自 _Ref_count_base
template <class _UxptrOrNullptr, class _Dx>
void _Setpd(const _UxptrOrNullptr _Px, _Dx _Dt) { // take ownership of _Px, deleter _Dt
_Temporary_owner_del<_UxptrOrNullptr, _Dx> _Owner(_Px, _Dt);
_Set_ptr_rep_and_enable_shared(
_Owner._Ptr, new _Ref_count_resource<_UxptrOrNullptr, _Dx>(_Owner._Ptr, _STD move(_Dt)));
_Owner._Call_deleter = false;
}
主要是 Destroy
虚函数与 _Ref_count
实现的不同,其保存了一个 _Compressed_pair<_Dx, _Resource>
而不仅仅是一个原始指针
private:
void _Destroy() noexcept override { // destroy managed resource
_Mypair._Get_first()(_Mypair._Myval2);
}
void _Delete_this() noexcept override { // destroy self
delete this;
}
_Compressed_pair<_Dx, _Resource> _Mypair;
那么再 _Destroy
时,就是把 deletor
取出来,然后以原始指针为参数传入调用
2024/9/19更新
补充一下构建共享指针的另一种方式 make_shared
,一般使用方式如下:
class A {
public:
A() = default;
A(int a, double b) : _a(a), _b(b) {}
int _a;
double _b;
};
int main()
{
shared_ptr<A> sptr = make_shared<A>(1, 2.0);
}
网上好多资料说 make_shared
相较于先 new
再创建共享指针可以少 new
一次,那么具体内部是如何实现的?
下面为 MSVC 中的代码,可以看到这里只有一个 new
创建了一个 _Ref_count_obj2<_Ty>
对象。
template <class _Ty, class... _Types>
shared_ptr<_Ty>
make_shared(_Types&&... _Args) { // make a shared_ptr to non-array object
const auto _Rx = new _Ref_count_obj2<_Ty>(_STD forward<_Types>(_Args)...);
shared_ptr<_Ty> _Ret;
_Ret._Set_ptr_rep_and_enable_shared(_STD addressof(_Rx->_Storage._Value), _Rx);
return _Ret;
}
看下 _Ref_count_obj2
内部,可以看到,其和 _Ref_count
一样,也是继承自 _Ref_count_base
,当然因为是直接构造,所以与 _Ref_count
保存的是原始指针不同,_Ref_count_obj2
保存了对象本身,即 _Storage._Value
。
template <class _Ty>
class _Ref_count_obj2 : public _Ref_count_base { // handle reference counting for object in control block, no allocator
public:
template <class... _Types>
explicit _Ref_count_obj2(_Types&&... _Args) : _Ref_count_base() {
{
_Construct_in_place(_Storage._Value, _STD forward<_Types>(_Args)...);
}
}
~_Ref_count_obj2() noexcept override { // TRANSITION, should be non-virtual
// nothing to do, _Storage._Value was already destroyed in _Destroy
// N4849 [class.dtor]/7:
// "A defaulted destructor for a class X is defined as deleted if:
// X is a union-like class that has a variant member with a non-trivial destructor"
}
union {
_Wrap<_Ty> _Storage;
};
private:
void _Destroy() noexcept override { // destroy managed resource
_Destroy_in_place(_Storage._Value);
}
void _Delete_this() noexcept override { // destroy self
delete this;
}
};
深入看下构造函数中的 _Construct_in_place
,其实就是个 placement new
在对象所在内存位置进行构造(调用对象的构造函数)。
template <class _Ty, class... _Types>
void _Construct_in_place(_Ty& _Obj, _Types&&... _Args)
::new (_Voidify_iter(_STD addressof(_Obj))) _Ty(_STD forward<_Types>(_Args)...);
}
相应的 _Destroy
中的 _Destroy_in_place
也就是调用对象的析构函数。
template <class _Ty>
void _Destroy_in_place(_Ty& _Obj) noexcept {
_Obj.~_Ty();
}
到这就能看出 make_shared
内部是怎么实现的了,最后我们来测试下与先 new
后创建共享指针方式的时间对比。
下面为测试代码
#include <iostream>
#include <chrono>
using namespace std;
class A {
public:
A() = default;
A(int a, double b) : _a(a), _b(b) {}
int _a;
double _b;
};
int main()
{
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 100000000; i++) // 1e8
shared_ptr<A> sptr = make_shared<A>(1, 2.0);
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
std::cout << duration.count() << "ms" << std::endl;
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 100000000; i++)
shared_ptr<A> sptr(new A(1, 2.0));
stop = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
std::cout << duration.count() << "ms" << std::endl;
}
weak_ptr
与 shared_ptr
一样,weak_ptr
也是继承自 _Ptr_base
_EXPORT_STD template <class _Ty>
class weak_ptr : public _Ptr_base<_Ty>
看下拷贝构造函数,都是调用基类的 _Weakly_construct_from
weak_ptr(const weak_ptr& _Other) noexcept {
this->_Weakly_construct_from(_Other); // same type, no conversion
}
template <class _Ty2, enable_if_t<_SP_pointer_compatible<_Ty2, _Ty>::value, int> = 0>
weak_ptr(const shared_ptr<_Ty2>& _Other) noexcept {
this->_Weakly_construct_from(_Other); // shared_ptr keeps resource alive during conversion
}
根据 _Other.Rep
是否为空执行不同分支,如果非空,当前持有相同的原始指针和引用对象,且增加 引用对象的weak引用计数
template <class _Ty2>
void _Weakly_construct_from(const _Ptr_base<_Ty2>& _Other) noexcept { // implement weak_ptr's ctors
if (_Other._Rep) {
_Ptr = _Other._Ptr;
_Rep = _Other._Rep;
_Rep->_Incwref();
} else {
_STL_INTERNAL_CHECK(!_Ptr && !_Rep);
}
}
再看一下 weak_ptr
常用的几个方法的实现
use_count
获取原始指针的引用计数,非weak引用计数
// inline long std::_Ptr_base<_Ty>::use_count() const
_NODISCARD long use_count() const noexcept {
return _Rep ? _Rep->_Use_count() : 0;
}
//inline long std::_Ref_count_base::_Use_count() const
long _Use_count() const noexcept {
return static_cast<long>(_Uses);
}
本质就是返回引用对象里的 _Uses
值,所以当一个Weak_Ptr
指向的共享指针生命周期都结束了,use_count
就为 0 了。
expired
也是同理,只是一个_Uses
值是否为 0 的判断
_NODISCARD bool expired() const noexcept {
return this->use_count() == 0;
}
lock
获取共享指针
_NODISCARD shared_ptr<_Ty> lock() const noexcept { // convert to shared_ptr
shared_ptr<_Ty> _Ret;
(void) _Ret._Construct_from_weak(*this);
return _Ret;
}
定义一个空构造的共享指针,然后调用基类的 _Construct_from_weak
template <class _Ty2>
bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other) noexcept {
// implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock()
if (_Other._Rep && _Other._Rep->_Incref_nz()) {
_Ptr = _Other._Ptr;
_Rep = _Other._Rep;
return true;
}
return false;
}
引用对象的指针非空,且对其引用计数增加的结果不为0,就把对应的原始指针和引用对象指针传给共享智能指针。_Incref_nz
的实现也是考虑到线程安全的,使用CAS来避免竞争,这里的CAS就是比较交换原子操作,比较成功这里是 _Volatile_uses
和 _Count
的比较则将 _Count+1
写入目标位置,否则不写入,最后都要返回 _Volatile_uses
原本的值。如果_Count!=0
则返回true,否则返回false(比如最后一个共享指针释放先执行了,那么此时_Volatile_uses=0
,那么比较失败返回了0,原本_Count=1
,此时while循环中止,返回false)。
bool _Incref_nz() noexcept { // increment use count if not zero, return true if successful
auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
#ifdef _M_CEE_PURE
long _Count = *_Atomic_address_as<const long>(&_Volatile_uses);
#else
long _Count = __iso_volatile_load32(reinterpret_cast<volatile int*>(&_Volatile_uses));
#endif
while (_Count != 0) {
const long _Old_value = _INTRIN_RELAXED(_InterlockedCompareExchange)(&_Volatile_uses, _Count + 1, _Count);
if (_Old_value == _Count) {
return true;
}
_Count = _Old_value;
}
return false;
}
最后看下 weak_ptr
的析构函数,就是调用引用对象的 _Decwref
方法
~weak_ptr() noexcept {
this->_Decwref();
}
unique_ptr
unique_ptr
独占所有权,其不需要引用对象,所以没有继承 _Ptr_base
,此外
_EXPORT_STD template <class _Ty, class _Dx /* = default_delete<_Ty> */>
class unique_ptr { // non-copyable pointer to an object
其拷贝构造函数和拷贝赋值运算符都是被删除的
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
有默认deletor和指定deletor两种构造函数
template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
_CONSTEXPR23 explicit unique_ptr(pointer _Ptr) noexcept : _Mypair(_Zero_then_variadic_args_t{}, _Ptr) {}
template <class _Dx2 = _Dx, enable_if_t<is_constructible_v<_Dx2, const _Dx2&>, int> = 0>
_CONSTEXPR23 unique_ptr(pointer _Ptr, const _Dx& _Dt) noexcept : _Mypair(_One_then_variadic_args_t{}, _Dt, _Ptr) {}
主要看下移动构造函数
template <class _Dx2 = _Dx, enable_if_t<is_move_constructible_v<_Dx2>, int> = 0>
_CONSTEXPR23 unique_ptr(unique_ptr&& _Right) noexcept
: _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx>(_Right.get_deleter()), _Right.release()) {}
_Right.release()
,就是和空进行交换,并返回原有的指针
_CONSTEXPR23 pointer release() noexcept {
return _STD exchange(_Mypair._Myval2, nullptr);
}
最后看下析构函数,如果原始指针非空,deletor释放资源
_CONSTEXPR23 ~unique_ptr() noexcept {
if (_Mypair._Myval2) {
_Mypair._Get_first()(_Mypair._Myval2);
}
}