C++内存管理:shared_ptr/weak_ptr源码(长文预警)

本文主要讲述c++提供的 shared_ptrweak_ptrmake_shared和 enable_shared_from_this,以及详解MSVC中STL提供的智能指针源码。

具体使用方法这里不会从头开始介绍,可以参考这篇文章:深度学习可好玩了:c++:RAII与智能指针

原理

有关智能指针的实现,我认为主要核心在于以下内容:

  1. 如何破除循环引用
  2. 何时引用计数改变
  3. 何时释放内存资源
  4. 如何保障线程安全

最后再介绍 make_shared和 enable_shared_from_this的原理。

如何破除循环引用

首先c++不会像python一样提供“部分标记-清除算法”来检测并破除循环引用,这对性能的影响太大。

我们再编写代码时,应该极力避免循环引用的情况。但是在订阅者模式或者观察者模式中,循环引用也是不可避免的,这时c++打出 shared_ptr和 weak_ptr这套组合拳。weak_ptr本身不控制资源,只是一个弱引用。

所以c++的智能指针其实并不支持两个引用都想掌控资源。但当一个掌握资源,另一个只是观察者即weak_ptr时,c++智能指针可以支持。

何时引用计数改变

为了破除循环引用,需要同时支持两个智能指针 shared_ptr和 weak_ptr,所以引用计数也要有两个:

  1. uses强引用,记录被多少个shared_ptr掌握;
  2. weaks弱引用,记录被多少个weak_ptr掌握,再+1;

uses何时改变:

  • shared_ptr构造时,+1;
  • shared_ptr拷贝赋值时,+1;
  • weak_ptr.lock(),发生weak到shared的晋级,+1;
  • shared_ptr析构时,-1;

weaks何时改变:

  • weak_ptr构造时,+1;
  • weak_ptr拷贝赋值时,+1;
  • weak_ptr析构时,-1;
  • 当uses减为0时,weaks-1;

我们会发现weaks,在初始化时会多加一个1,所以当uses减为0时,weaks再-1。这样就平衡了。之所以加1是为了保持接口的一致性。

两个引用大体上是独立的,除了 weak_ptr.lock()这个特例,发生了晋级。

何时释放内存资源

我们知道一个智能指针的实现最起码包括:指针(指向资源)、强引用、弱引用这三个数据成员。

所以我们需要释放两块内存,分别是数据块和控制块。

  • 由于弱引用是不控制数据块资源的,所以是否释放数据块仅需观察强引用uses,当uses==0时,释放数据块;
  • 至于控制块的释放,自然是要等uses ==0且weaks也 ==0时,还记得吗,在分析引用计数何时改变时,介绍到weaks初始化会多加1,当uses ==0时,再--。因此当weaks ==0时,uses必 ==0,所以只需判断weaks是否 ==0即可。

通过上面的分析,得出一个注意事项,那就是weak_ptr在使用前很可能uses==0,导致数据块已经被释放了,因此weak_ptr并不控制数据块的生命周期。

如何保障线程安全

线程安全的智能指针是进阶知识,智能指针的线程安全只保障到引用计数,也就是说引用计数的变化是线程安全的,但智能指针中的指针是不安全的,我们可以举一个很简单的例子。

// global
std::shared_ptr<Foo> g_ptr;

// 线程1
std::shared_ptr<Foo> x;
x = g_ptr;

// 线程2
std::shared_ptr<Foo> y;
g_ptr = y

线程1在赋值操作之前,很有可能g_ptr已经被更改了。

因此我们可以说出智能指针中很经典的一句话:

  1. 多个线程同时读写多个(值传递)shared_ptr 实例是线程安全的;
  2. 多个线程同时读写一个 shared_ptr 实例是非线程安全的;

在多线程编程中,我们应该用值传递构造一个新的share_ptr,这样就确保是安全的,引用计数本身就是安全的,无需care,而值传递又确保了智能指针中的指针也是安全的。

注意:上面都是介绍shared_ptr自身的线程安全性,并不是shared_ptr管理资源的安全性。


讲完shared_ptr的多线程安全性,我们来看看它是怎么实现引用计数的线程安全,主要有两个手段:

  • 引用计数的加减都是原子操作
  • weak_ptr.lock()通过CAS(Compare and Swap)操作实现自旋锁

第一个点很简单不介绍了,我们主要讲第二个点。

lock是weak_ptr获得shared_ptr的权限,发生了weak到shared的晋级,在lock之前需要判断uses是否 ==0;如果uses ==0,那么lock将失败,因为这时资源不再有效。

为了实现线程安全,我们需要在lock前判断是否有别的线程更改了uses,并且要求uses!=0。这里采用while循环的自旋锁是个好主意,不断判断条件,满足条件后uses+1,当然这还不够,还需要保证 从判断条件到uses+1这段代码是原子操作,即不可中断。这就引入了CAS(Compare and Swap),是各个cpu都支持的、从硬件上实现的原子操作。

如windows提供的API_InterlockedCompareExchange(当前值,新值,期望值)

若当前值和期望值相同,则当前值更新为新值;
若当前值和期望值不相同,则什么也不做;
返回第一个参数的原始值。

下面我们来看一下完整代码,lock最终会调用_Incref_nz函数。

 bool _Incref_nz() noexcept {
     /* 
     该方法是后续weak_ptr.lock()线程安全的基石,
     weak_ptr.lock()会把weak_ptr晋级为shared_ptr,而在晋级过程中,如果uses已经降为0,还将发生不预期的行为
     所以在晋级过程中,必须保证uses始终不为0。这里采用CAS操作(原子操作不可中断)
     本质上这段代码实现自旋锁,一种不阻塞的锁,cpu执行会一直循环
    */
     auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
     // 强制转换为volatile&,为了限制编译器对其进行优化,volatile使得每次读取_Uses时,必须从内存中重新读取
     long _Count = _ISO_VOLATILE_LOAD32(_Volatile_uses); // 创建新副本
 ​
     while (_Count != 0) {
         // while所以这段代码会一直循环,实现自旋锁
         // 当_Count==0,说明uses置0了,将会lock失败
         
         const long _Old_value = 
             _INTRIN_RELAXED(_InterlockedCompareExchange)(&_Volatile_uses, _Count + 1, _Count);
         if (_Old_value == _Count) { // +1成功
             return true;
         }
         _Count = _Old_value; // +1失败,更新期望值_Count
     }
 ​
     return false;
 }

上面代码中,1要保证++是原子操作,2要保证_Uses不为0。

_Count是_Uses的副本,就是期望值,若+1成功直接返回;若+1失败,要更新期望值_Count(即第21 line)。

这里通过判断期望值确定是否为0。

enable_shared_from_this

从名字可以看出来enable_shared_from_this,是为了让shared_ptr支持this指针,这在回调中比较常见。

如果直接用shared_ptr管理this指针,会导致多次delete释放资源。正确的做法是继承于enable_shared_from_this。

先看错误案例:

class Book {
  public:
    std::shared_ptr<Book> getptr() {
       return std::shared_ptr<Book>(this);
    }
};
Book *data = new Book(3);
std::shared_ptr<Book> p1 = data->getptr();
std::shared_ptr<Book> p2 = data->getptr();

此时p1和p2都控制data,但它们的引用计数是独立的,因为每次调用getptr() return的都是一个新的shared_ptr,具有新的引用计数。这就导致了p1和p2各释放一次内存。

来看正确做法:

struct Book: std::enable_shared_from_this<Book> 
{
    std::shared_ptr<Book> getptr() {
        return shared_from_this();
    }
};
auto data = std::make_shared<Book>(); // 这一步非常重要
std::shared_ptr<Book> p1 = data.getptr();
std::shared_ptr<Book> p2 = data.getptr();

我们发现Book继承了enable_shared_from_this,然后再调用shared_from_this方法。

原理其实很简单enable_shared_from_this这个基类具有一个数据成员 _Wptr(weak_ptr类型),每次shared_from_this都是拷贝构造内部的 _Wptr,这样它们的引用计数是共享的,不是独立的。

重点:第7行代码 auto data = std::make_shared<Book>();很重要。想要正确的使用 enable_shared_from_this就请先构造一个shared_ptr。不可以是构造一个unique_ptr,更不可以是栈上的变量如 Book data; 。

这有两方面原因:

  1. 栈上变量,绝不能用智能指针控制,智能指针只能控制堆上变量。因为栈会自动析构,如果用智能指针包装,会导致多次析构;
  2. enable_shared_from_this内部的weak_ptr是在第7行才初始化,即构造shared_ptr时,这很有意思,也就是说内部的_Wptr并不是在enable_shared_from_this构造函数中初始化。原因是如果不用到 _Wptr,那么没必要初始化以节省开销。到源码再仔细介绍。

总之在使用时,我们记住就行,要想要正确的使用 enable_shared_from_this,就请先构造一个shared_ptr。

make_shared

make_shared是构造shared_ptr,它相对于 shared_ptr<T>(new T)有两个好处:

  1. 开销更小,make_shared只需内存分配一次;
  2. 是异常安全的;

对于第一个点make_shared只需内存分配一次

我们知道内存分配是一件比较麻烦的事情,找连续内存很耗时,而且会导致内存碎片。能一次性一块分配了就不要分成两次。

对于第二个点异常安全,参考《effective c++》

来看一个例子:

processWidget(std::shared_ptr<Widget>(new Widget), funct());

该函数有3部分 std::shared_ptr<Widget>构造函数new Widget动态分配,funct()

c++对它们3个的执行顺序是不确定的,这就意味着,有可能是以下顺序执行的:

  1. new Widget动态分配;
  2. funct()
  3. std::shared_ptr<Widget>构造函数

此时如果funct()抛出异常,那么就会发生内存泄漏!解决方法有两个:一、用make_shared,因为一次性分配不存在分步执行;二、将new用独立语句执行,比如:

std::shared_ptr<Widget> pw(new Widget)
processWidget(pw, funct());

那make_shared这么好,有什么缺点呢?有一个延迟释放资源的缺点,因为make_shared是将数据块和控制块一起分配内存,好兄弟同生同死,所以也要一起释放。那么即便_Uses减为0了,_Weaks不为0,数据块资源依旧无法释放。直到_Weaks为0。

源码

本文源码参考来自MSVC,github地址

其实一开始并不建议直接看STL中的源码,因为STL是模板编程,要考虑很多情况。在看STL源码之前,可以先看简单的智能指针实现,对其中的七大件:构造\拷贝构造\移动构造\析构\赋值\拷贝赋值\移动赋值,做到心知肚明。

在看STL源码之前最好对:copy and swap惯用法、模板元编程(类型萃取,SFINAE,enable_if_t,void_t等),c++11特性(=default、=delete、using用法、就地初始化、noexcept用法、移动语义、完美转发等)有所了解,当然边看边学也是个好主意。

智能指针整体的类图见下,可以发现 share_ptr和 weak_ptr只是对外提供一些api,主要都是由 Ptr_base实现,尤其是这6个构造函数,其中 Ptr_base的 _rep数据成员很重要,它是一个 Ref_count_base指针,指向控制块。值得注意的是 _rep必须是指针,这样可以通过指针调用虚函数来实现 _Delete_this和 _Destroy多态。

控制块的数据成员有:指向数据块的指针、两个引用计数。函数成员有:引用计数的加减、释放数据块资源、释放控制块自身资源等。控制块有三种类型:Ref_countRef_count_delRef_count_del_alloc,看名字就知道它们分别支持单独的指针、指针+自定义删除器、指针+自定义删除器+分配器。它们都有一个共同的基类 Ref_count_base

控制块

下面我们先看控制块部分的代码:

主要关心 _Destory()和 _Delete_this()这两个虚函数:

  • _Destory() 释放指针指向资源;
  • _Delete_this()释放控制块自身的资源;

此外 _Uses和 _Weaks都是原子操作的加减,确保引用计数是线程安全的。至于_Incref_nz实现的自旋锁也是线程安全的基石,之前讲过,这里就不讲了。

具体代码:

// 控制块的抽象基类
class Ref_count_base {
private:
    // c++11就地初始化
    std::_Atomic_counter_t _Uses = 1;
    std::_Atomic_counter_t _Weaks = 1; // 在初始化时多加1
  
    // 虚函数多态特性,不同派生类有不同的表现
    virtual void _Destory() noexcept = 0; // 释放ptr指向资源 
    virtual void _Delete_this() noexcept = 0; // 释放控制块自身的资源

public:
    virtual ~Ref_count_base() noexcept {}; 
    Ref_count_base() = default;

    Ref_count_base(const Ref_count_base&) = delete;
    Ref_count_base& operator=(const Ref_count_base&) = delete;

    bool _Incref_nz() noexcept {
        auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
        long _Count = _Volatile_uses;
        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;
    }

    void _Incwref() noexcept {
        _MT_INCR(_Weaks); //原子操作
    }

    void _Decwref() noexcept {
        if (_MT_DECR(_Weaks) == 0) {
            _Delete_this();
        }
    }

    void _Incref() noexcept {
        _MT_INCR(_Uses);
    }

    void _Decref() noexcept { // 
        if (_MT_DECR(_Uses) == 0) {
            // 当uses减为0时,那么释放ptr的资源,并且weak--,因为在初始化时weak多加了1
            _Destory();
            _Decwref();
        }
    }

    long _Use_count() const noexcept {
        return static_cast<long>(_Uses);
    }
};

template <class Ty>
class Ref_count : public Ref_count_base {
private:
    Ty* _ptr;

    virtual void _Destroy() noexcept override {
        delete _ptr;
    }

    virtual void _Delete_this() noexcept override {
        delete this;
    }

public:
    explicit Ref_count(Ty* ptr) :_ptr(ptr) {}

};

template <class Ty,class Dy>
class Ref_count_del : public Ref_count_base {
private:
    Ty* _ptr;
    Dy _dtor;
  
    virtual void _Destroy() noexcept override {
        _dtor(_ptr); // 调用自定义的deleter
    }

    virtual void _Delete_this() noexcept override {
        delete this;
    }

public:
    Ref_count_del(Ty* ptr, Dx dtor): _ptr(ptr), _dtor(dtor){}

};

// TODO 支持Ref_count_del_alloc

Ptr_base

Ptr_base实现了足足有六个construct函数,这些函数将在shared_ptr和weak_ptr构造时调用。

可以发现 _Construct_from_weak构造函数调用了 _Incref_nz实现线程安全的晋级。

还有一类比较特殊的 _Alias_construct_from别名构造函数,它给我们提供了部分对象管理的功能,详见文章

在编写构造函数时,我们主要关心引用计数的变化。

具体代码:

template <class Ty>
class Ptr_base {
public:
    Ptr_base() = default; 
    ~Ptr_base() = default;

    using element_type = std::remove_extent_t<Ty>;

    long use_count() const noexcept {
        return _rep ? _rep->_Use_count() : 0 ;
    }

    element_type* get() const noexcept { return _ptr; }

protected:
    template <class Ty2> 
    void _Move_construct_from(Ptr_base<Ty2>&& other) noexcept {
        _ptr = other._ptr;
        _rep = other._rep;

        other._ptr = nullptr;
        other._rep = nullptr;
    }

    template <class Ty2>
    void _Copy_construct_from(const Ptr_base<Ty2>& other) noexcept {
        if (other._rep) {
            other._rep->Incref();
        }

        _ptr = other._ptr;
        _rep = other._rep;
    }

    template <class Ty2>
    void _Alias_construct_from(const Ptr_base<Ty2>& other, element_type* ptr) noexcept {
        if (other._rep) {
            other._rep->Incref();
        }

        _ptr = _ptr;
        _rep = other._rep;
    }

    template <class Ty2>
    void _Alias_move_construct_from(Ptr_base<Ty2>&& other, element_type* ptr) noexcept {
        _ptr = _ptr;
        _rep = other._rep;

        other._ptr = nullptr;
        other._rep = nullptr;
    }
  
    // weak->shared
    template <class Ty2>
    bool _Construct_from_weak(const Ptr_base<Ty2>& other) noexcept {
        if (other._rep && other._rep->_Incref_nz()) { // 要确保_rep有效,且uses!=0
            _ptr = other._ptr;
            _rep = other._rep;
            return true;
        }
        return false;
    }

    // shared->weak
    template <class Ty2>
    void _Weakly_construct_from(const Ptr_base<Ty2>& other) noexcept {
        if (other._Rep) {
            _ptr = other._ptr;
            _rep = other._rep;
            _rep->_Incwref();
        }
    }

    void _Swap(Ptr_base& other){
        using namespace std;
        std::swap(_ptr, other._ptr);
        std::swap(_rep, other._rep);
    }

    void _Decwref() noexcept { 
        if (_rep) {
            _rep->_Decwref();
        }
    }

    void _Decref() noexcept { 
        if (_rep) {
            _rep->_Decref();
        }
    }

private:
    element_type* _ptr = nullptr;
    Ref_count_base* _rep = nullptr;
};

类型萃取

因为STL是模板编程吗,类型萃取是不可避免的,这里罗列一下主要用到的类型萃取的模板类,完全可以跳过!我们主要是研究智能指针。

template <class... _Types>
using void_t = void; // void_t技巧,常用于类型检查

// 是否可以delete p;
template <class Ty, class = void>
struct Can_scalar_delete : std::false_type {};
template <class Ty>
struct Can_scalar_delete<Ty, void_t<decltype(delete std::declval<Ty*>())>> : std::true_type {};

// 是否可以delete[] p;
template <class Ty, class = void>
struct Can_array_delete : std::false_type {};
template <class Ty>
struct Can_array_delete<Ty, void_t<decltype(delete[] std::declval<Ty*>())>> : std::true_type {};

// 
template <class Ty, class _Ty>
struct SP_convertible : std::is_convertible<Ty*, _Ty*>::type {};
template <class Ty, class Uty>
struct SP_convertible<Ty, Uty[]> : std::is_convertible<Ty(*)[], Uty(*)[]>::type {};
template <class Ty, class Uty, size_t Ext>
struct SP_convertible<Ty, Uty[Ext]> : std::is_convertible<Ty(*)[Ext], Uty(*)[Ext]>::type {};

// 这在传递自定义的删除器时用于检测参数是否callable
template <class Fx, class Arg, class = void>
struct Can_call_function_object : std::false_type {};
template <class Fx, class Arg>
struct Can_call_function_object<Fx, Arg, void_t<decltype(std::declval<Fx>()(std::declval<Arg>()))>> : std::true_type {};

// 两个Type是否是兼容的,这当赋值函数的两个type不同时用到,要确保两个type是兼容的
template <class Yty, class Ty>
struct SP_pointer_compatible : std::is_convertible<Yty*, Ty*>::type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], Uty[]> : std::true_type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], const Uty[]> : std::true_type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], volatile Uty[]> : std::true_type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], const volatile Uty[]> : std::true_type {};

// 是否继承于enable_shared_from_this,这在shared_ptr构造函数时用到
// 主要是看有没有定义_Esft_type类型
template <class _Yty, class = void>
struct Can_enable_shared : std::false_type {}; 
template <class _Yty>
struct Can_enable_shared<_Yty, void_t<typename _Yty::_Esft_type>>
    : std::is_convertible<std::remove_cv_t<_Yty>*, typename _Yty::_Esft_type*>::type {};

using std::is_array_v;
using std::negation;
using std::is_volatile;

shared_ptr

shared_ptr 继承于Ptr_base,只是支持对外的一些API。

本文代码是支持数组的,在c++17之前,如果传递的是数组,需要自定义删除器,比如

std::shared_ptr<int>ptr(new int[10],std::default_delete<int[]>());

在c++17之后,shared_ptr支持了数组,无需自定义删除器,原理是在构造函数时,通过is_array_v类型萃取实现的。

整个代码用了大量类型萃取,可以忽略,不影响shared_ptr的本质。

主要是注意构造函数调用的 _Set_ptr_rep_and_enable_shared方法,这可能让很多人看的一头雾水,Set_ptr_rep可以理解,就是设置ptr,并且new一个控制块,但enable_shared是啥意思?这必须要看到 enable_shared_from_this类,才能讲明白。总之 _Set_ptr_rep_and_enable_shared会判断如果“构造函数传进来的指针”指向的是一个 enable_shared_from_this类,那么它会初始化这个类的 _Wptr指针!再次回忆起,在讲解原理时我们就提到 enable_shared_from_this是在构造shared_ptr时才初始化内部的 _Wptr

_Set_ptr_rep_and_enable_shared需要反复看,联系起enable_shared_from_this代码和用法。

具体代码:

template <class Ty>
class shared_ptr : public Ptr_base<Ty> {
private:
    using Mybase = Ptr_base<Ty>;

public:
    using typename Mybase::element_type;

    // 构造函数
    shared_ptr() noexcept {}

    shared_ptr(nullptr_t) noexcept :_ptr(nullptr_t),_rep(nullptr_t) {}

    template <class Ux,
        typename = std::enable_if_t<std::conjunction_v< 
            is_array_v<Ty>,Can_array_delete<Ux>,Can_scalar_delete<Ux>,SP_convertible<Ux,Ty> >
        >>
    explicit shared_ptr(Ux* ptr){
        if constexpr (is_array_v<Ty>) { // c++17后开始支持数组
            _Setpd(ptr, default_delete<Ux[]>{});
        }
        else {
            _Set_ptr_rep_and_enable_shared(ptr, new Ref_count<Ux>(ptr));
        }
    }

    template <class Ux, class Dx,
        typename = std::enable_if_t<std::conjunction_v<
            is_move_constructible<Dx>,Can_call_function_object<Dx&, Ux*&>,SP_convertible<Ux, Ty> >
        >>
    shared_ptr(Ux* ptr,Dx dtor) {
        _Setpd(ptr, std::move(dtor));
    }

    template <class Dx,
        typename = std::enable_if_t<std::conjunction_v<
            is_move_constructible<Dx>, Can_call_function_object<Dx&, nullptr_t&>>
        >>
    shared_ptr(nullptr_t, _Dx _Dt) {
        _Setpd(nullptr, std::move(dtor));
    }

    // TODO 支持alloc参数
  
    template <class Ty2> // alias construction 并不要求Ty2和Ty兼容,因为alias应用场景是类和成员
    shared_ptr(const shared_ptr<Ty2>& other, element_type* ptr) noexcept {
        this->_Alias_construct_from(other, ptr);
    }

    template <class Ty2>
    shared_ptr(shared_ptr<Ty2>&& other, element_type* ptr) noexcept {
        this->_Alias_move_construct_from(std::move(other), ptr);
    }

    shared_ptr(const shared_ptr& other) noexcept {
        this->_Copy_construct_from(other);
    }

    template <class Ty2 // 要求Ty2和Ty兼容
        typename = std::enable_if_t<SP_pointer_compatible<Ty2, Ty>::value>>
    shared_ptr(const shared_ptr<Ty2>& other) noexcept {
        this->_Copy_construct_from(other);
    }

    shared_ptr(shared_ptr&& other) {
        this->_Move_construct_from(std::move(other));
    }

    template <class Ty2,
        typename = std::enable_if_t<SP_pointer_compatible<Ty2, Ty>::value>>
    shared_ptr(shared_ptr<Ty2>&& other) {
        this->_Move_construct_from(std::move(other));
    }

    // 从weak晋级到shared,要确保uses不等于0,_Construct_from_weak要安全
    template <class Ty2,
        typename = std::enable_if_t<SP_pointer_compatible<Ty2, Ty>::value>>
    explicit shared_ptr(const weak_ptr<Ty2>& other) {
        if (!this->_Construct_from_weak(other)) {
            throw exception("bad weak ptr");
        }
    }

    // 析构函数,引用计数--
    ~shared_ptr() noexcept { 
        this->_Decref();
    }

    void swap(shared_ptr& _Other) noexcept {
        this->_Swap(_Other);
    }

    // 赋值函数 copy and swap技巧
    shared_ptr& operator=(shared_ptr tmp) noexcept {
        tmp.swap(*this);
        return *this;
    }

    template <class Ty2>
    shared_ptr& operator=(shared_ptr<Ty2> tmp) noexcept {
        tmp.swap(*this);
        return *this;
    }

    // 释放指针,并置为空,巧妙用swap代码复用
    void reset() noexcept { 
        shared_ptr().swap(*this);
    }

    // 释放指针,并置为ptr
    template <class Ux>
    void reset(Ux* ptr) {
        shared_ptr(ptr).swap(*this);
    }

    template <class Ux, class Dx>
    void reset(Ux*ptr, Dx dtor) { 
        shared_ptr(ptr, dtor).swap(*this);
    }

    using Mybase::get;

    // 取指针操作
    element_type& operator*() const noexcept {
        return *get();
    }

    element_type* operator->() const noexcept {
        return get();
    }

    template <typename = std::enable_if_t<is_array_v<Ty>>>
    element_type& operator[](ptrdiff_t _Idx) const noexcept {
        return get()[_Idx];
    }

    bool unique() const noexcept {
        return this->use_count() == 1;
    }

    explicit operator bool() const noexcept {
        return get() != nullptr;
    }

private:
    template <class Uxptr, class Dx>
    void _Setpd(const Uxptr ptr, Dx dtor) {
        _Set_ptr_rep_and_enable_shared(ptr, new Ref_count_del<Uxptr, Dx>(ptr, std::move(dtor)));
    }

    // TODO支持 alloc参数 _Setpda

    // 支持enable_from_shared_this的核心
    template <class Ux>
    void _Set_ptr_rep_and_enable_shared(Ux* const ptr, Ref_count_base* const rep) noexcept { 
        this->_ptr = ptr;
        this->_rep = rep;
        if constexpr (std::conjunction_v<negation<is_array<Ty>>, negation<is_volatile<Ux>>, Can_enable_shared<Ux>>) { // Can_enable_shared在编译期判断是否继承于enable_from_shared_this
            if (ptr && ptr->_Wptr.expired()) {
                ptr->_Wptr = shared_ptr<std::remove_cv_t<Ux>>(*this, const_cast<std::remove_cv_t<Ux>*>(ptr)); // enable_from_shared_this的_Wptr此时才构造
            }
        }
    }

    void _Set_ptr_rep_and_enable_shared(nullptr_t, Ref_count_base* const rep) noexcept { 
        this->_ptr = nullptr;
        this->_rep = rep;
    }

    template <class _Ty0, class... _Types>
    friend shared_ptr<_Ty0> make_shared(_Types&&... _Args);
};

enable_from_shared_this

代码很简单,会发现enable_shared_from_this根本就没有构造函数来初始化 _Wptr,再次证明了需要在shared_ptr中初始化,因此想要正确使用enable_from_shared_this,要先构造shared_ptr。

template <class Ty>
class enable_shared_from_this {
public:
    using _Esft_type = enable_shared_from_this; 
    // Can_enable_shared类型萃取通过_Esft_type来判断是否继承于enable_from_shared_this

    shared_ptr<Ty> shared_from_this() {
        return shared_ptr<Ty>(_Wptr); // 都是拷贝构造
    }

    shared_ptr<const Ty> shared_from_this() const {
        return shared_ptr<const Ty>(_Wptr); // 拷贝构造
    }

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

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

protected:
    constexpr enable_shared_from_this() noexcept : _Wptr() {}

    enable_shared_from_this(const enable_shared_from_this&) noexcept : _Wptr() {}

    enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept {
        return *this;
    }

    ~enable_shared_from_this() = default;

private:
    template <class _Yty>
    friend class shared_ptr; // 需要声明友元

    mutable weak_ptr<_Ty> _Wptr; // 不在这里初始化
};

make_shared

make_shared就是重新定义了一个控制块,该控制块 Ref_count_obj2将包含数据块,这样控制块分配时,就会一块分配数据块和控制块。实现了一步走的功能。

也正因如此,make_shared有一个延迟释放资源的缺点,即便_Uses减为0了,_Weaks不为0,数据块资源依旧无法释放,直到_Weaks为0。

// STRUCT TEMPLATE _Wrap
template <class Ty>
struct _Wrap {
    Ty _Value; 
};

// 控制块:指针所指资源和控制块分配在同一内存
template <class Ty>
class Ref_count_obj2 : public Ref_count_base { 
public:
    template <class... Types>
    explicit Ref_count_obj2(Types&&... Args){
        std::_Construct_in_place(_Storage._Value, std::forward<Types>(Args)...);
        // 其实就是placement new原地构造函数
    }

    ~Ref_count_obj2() {} // 什么也不做,在_Destroy中析构

    union {
        _Wrap<Ty> _Storage;
    };

private:
    virtual void _Destroy() noexcept override { // destroy managed resource
        std::_Destroy_in_place(_Storage._Value); // 其实就是placement delete
    }

    virtual void _Delete_this() noexcept override { // destroy self
        delete this;
    }
};

template <class Ty, class... Types>
shared_ptr<Ty> make_shared(Types&&... Args) { // make a shared_ptr
    const auto Ref = new Ref_count_obj2<Ty>(std::forward<Types>(Args)...);
    shared_ptr<Ty> result;
    result._Set_ptr_rep_and_enable_shared(std::addressof(Ref->_Storage._Value), Ref);
    return result;
}

C++内存管理:shared_ptr/weak_ptr源码(长文预警) - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值