C++深入剖析智能指针

本文深入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_thisRef_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 常用的几个方法的实现

  1. 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 了。

  1. expired 也是同理,只是一个 _Uses 值是否为 0 的判断
    _NODISCARD bool expired() const noexcept {
        return this->use_count() == 0;
    }
  1. 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);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值