探究share_ptr的底層實現(原創)


探究share_ptr的底層實現

本人大二菜雞, 源碼看不懂的地方會查資料, 應該大部分人都看得懂
最重要的地方在最后面
首先我們寫

class A

{
}

int main()
{
std::cout << "Hello World!\n";
shared_ptr a;
}

然后ctrl+click進入shared_ptr

是長這樣的

展開后發現繼承了class shared_ptr : public _Ptr_base<_Ty>

我們先直接看share_ptr 遇到沒見過的成員再去看基類

1 總體概況

沒啥, using 那句話用法就是

using db =double

db a=1.25


constexpr 就是 const + expression

編譯時期確定的表達式

可以看到注釋是 空的構造函數

2 構造函數探秘

那么接下來在那兩個空的構造函數后面的這個template有一大堆的就是真正的構造函數了 然后template里面主要有

template里面的東西不是很關心 主要關心構造函數, 但是還是大概看一下

enable_if_t 我猜應該是if(ptr) 這種吧

conjunction_v 和連接有關的什么

is_array_v _Can_array_delete

應該是因為delete 和delete [] 有區別吧

_Can_scalar_delete _SP_convertible就不知道是啥了 暫時忽略

ok ,接下來看函數體

大概結構就是這樣

template< >
explicit shared_ptr(_Ux* _Px) { 

}

看到有一行注釋

// construct shared_ptr object that owns _Px

所以猜測 px就是那個真正的指針

接下來看代碼

一行一行看

_Temporary_owner<_Ux> _Owner(_Px);

創建了一個臨時擁有px的owner對象

_Set_ptr_rep_and_enable_shared(_Owner._Ptr, new _Ref_count<_Ux>(_Owner._Ptr));

創建一個_Ref_count對象

這時候發現和剛才我們忽略的Temporary_owner聯系緊密

3.Temporary_Owner

幸運的是,這個類非常簡短

一行一行看

_Ux* _Ptr;

首先類成員有一個_Ptr

然后這是構造函數

explicit _Temporary_owner(_Ux* const Ptr) noexcept : _Ptr(Ptr) {}

無非就是把傳進來的東西賦給了剛才_Ptr

然后這兩行有點看不懂 什么=delete

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

然后一查 是C++11 新特性

https://www.ibm.com/developerworks/cn/aix/library/1212_lufang_c11new/index.html

可以看看這個 或者我直接說

對於 C++ 的類,如果程序員沒有為其定義特殊成員函數,那么在需要用到某個特殊成員函數的時候,編譯器會隱式的自動生成一個默認的特殊成員函數,比如拷貝構造函數,或者拷貝賦值操作符。

但是在某些情況下,假設我們不允許發生類對象之間的拷貝和賦值,可是又無法阻止編譯器隱式自動生成默認的拷貝構造函數以及拷貝賦值操作符,那這就成為一個問題了。

Deleted 函數的提出

為了能夠讓程序員顯式的禁用某個函數,C++11 標准引入了一個新特性:deleted 函數。程序員只需在函數聲明后加上“=delete;”,就可將該函數禁用。例如,我們可以將類 X 的拷貝構造函數以及拷貝賦值操作符聲明為 deleted 函數,就可以禁止類 X 對象之間的拷貝和賦值。

class X{            
    public: 
      X(); 
      X(const X&) = delete;  // 聲明拷貝構造函數為 deleted 函數
      X& operator = (const X &) = delete; // 聲明拷貝賦值操作符為 deleted 函數
    }; 
    
    

所以 _Temporary_owner 這個類就是ban掉了拷貝賦值函數和 拷貝構造函數

_Temporary_owner 臨時的主人嘛

最后

就是析構函數了

~_Temporary_owner() {
delete _Ptr;
}

沒什么好說的, OK 我們已經看完_Temporary_owner 類了

4.回到share_ptr的構造函數

現在我們知道了_Temporary_owner 就是個工具人

再回看這段代碼 還有哪里有疑惑呢

可能就是最關鍵的_Ref_count了

5.看_Ref_count

謝天謝地,_Ref_count也非常簡短

老規矩 一行一行看

聲明

template
class _Ref_count : public _Ref_count_base { // handle reference counting for pointer without deleter

繼承了_Ref_count_base 那么我們待會肯定要看這個類了

一個私有成員

_Ty* _Ptr;

構造函數

explicit _Ref_count(_Ty* _Px) : _Ref_count_base(), _Ptr(_Px) {}

調用了父類_Ref_count_base()的構造函數

_Ptr(_Px)然后還給私有成員_Ptr賦值

析構管理的資源

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

// destroy managed resource

析構自身

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

// destroy self

6.看_Ref_count_base

好了, 我們要看一個很關鍵的類了

我猜所有智能指針的引用技術都是繼承它的

非常不幸,這玩意非常長

先看個概況吧

展開后,

我們繼續來分析

折疊 非活動預處理器模塊

不看

兩個虛函數 =0表示是純虛函數嘛

要求子類實現

剛才我們看的_Ref_count對這兩個函數進行了實現 沒問題

那noexcept 什么意思呢

查了資料

從C++11開始,我們能看到很多代碼當中都有關鍵字noexcept。比如下面就是std::initializer_list的默認構造函數,其中使用了noexcept。 該關鍵字告訴編譯器,函數中不會發生異常,這有利於編譯器對程序做更多的優化。

更多用法就不討論了

兩個_Atomic_counter_t

_Atomic_counter_t _Uses = 1;
_Atomic_counter_t _Weaks = 1;

看名字就知道應該是原子的東西, 可能考慮到多線程對count的訪問問題

然后我抱着好奇心一看

索然無味,原來並沒有實現原子性 可能還要我們加鎖

繼續看下一部分

到了構造函數部分

protected:
   constexpr _Ref_count_base() noexcept = default; // non-atomic initializations

public:
   _Ref_count_base(const _Ref_count_base&) = delete;
   _Ref_count_base& operator=(const _Ref_count_base&) = delete;

這個和 =delete很像

Defaulted 函數的提出

為了解決兩個問題:1. 減輕程序員的編程工作量;2. 獲得編譯器自動生成的默認特殊成員函數的高的代碼執行效率,C++11 標准引入了一個新特性:defaulted 函數。程序員只需在函數聲明后加上“=default;”,就可將該函數聲明為 defaulted 函數,編譯器將為顯式聲明的 defaulted 函數自動生成函數體。

后面兩個也是ban掉拷貝構造函數和拷貝賦值函數

這個析構函數

virtual ~_Ref_count_base() noexcept {} // TRANSITION, should be non-virtual

翻譯一下: 過渡 應該是非虛函數

是還沒做好的意思嗎hhh

最重要的引用計數增加

函數返回值是bool

成功就返回true

auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
long _Count          = _ISO_VOLATILE_LOAD32(_Volatile_uses);

這倆行是把類型轉換一下 reinterpret_cast是啥我也不知道

查了大概了解一下

reinterpret_cast運算符是用來處理無關類型之間的轉換;它會產生一個新的值,這個值會有與原始參數(expressoin)有完全相同的比特位。

_ISO_VOLATILE_LOAD32這玩意就和編譯器所在系統的架構有關了 不是我們關注的地方

ok 現在來看while循環

const long _Old_value = _INTRIN_RELAXED(_InterlockedCompareExchange)(&_Volatile_uses, _Count + 1, _Count);

這話話的

_InterlockedCompareExchange是關鍵

我們看一下_InterlockedCompareExchange的源碼

__MACHINE(long __MACHINECALL_CDECL_OR_DEFAULT _InterlockedCompareExchange(long volatile * _Destination, long _Exchange, long _Comparand))

簡單來說 InterlockedCompareExchange屬於Interlocked系列互鎖函數之一,常用於多線程編程。

InterlockedCompareExchange是把目標操作數(第1參數所指向的內存中的數)與一個值(第3參數)比較,如果相等,則用另一個值(第2參數)與目標操作數(第1參數所指向的內存中的數)交換;

返回值是原始值

這樣我們就知道前面為什么要那兩句話了

 if (_Old_value == _Count) {
                return true;
            }

            _Count = _Old_value;

如果 由_InterlockedCompareExchange返回的值賦值給了_Old_value

如果_Old_value == _Count 說明沒有其他線程在此期間修改

我們返回true

否則 就把count返回到old_value

秒阿

_Ref_count_base的其他部分

分別是增加use和weak的

這個用來減引用計數 並且如果變成0 會調用析構

這個減weak

這個會返回User_count

這個暫時不知道干嘛

OK,到這里我們已經看完_Ref_count_base的所有代碼

OK,到這里我們已經看完_Ref_count_base的所有代碼

我們現在在哪? 我們現在在遞歸函數的最深層 現在該回頭 退出棧了 我們已經擁有了很多知識 不妨重新開始看

二.新的開始

一.構造函數

回顧一下 1035行之前的我們已經看過了

我們接着看

有deleter的構造函數

emm讓人想起了STL的alloater

話剛說完 allocater來了

一直到1102行 都是各種各樣的構造函數 從weak_ptr轉換的阿什么的,

害 ,平時用的時候老是抱怨怎么沒有實現這個到這個的重載, 現在看源碼知道了開發者的痛苦 數了一下 一共16個構造函數

二.析構函數

    ~shared_ptr() noexcept { // release resource
        this->_Decref();
    }

本篇最重要的內容

既然講到了析構,我們來講一下析構鏈,這一大堆析構函數到底是怎么執行的

class B {
public:
    ~B() {
        std::cout << "~B\n";
    }
};

class D : public B {
public:
    ~D() {
        std::cout << "~D\n";
    }
};

int main() {

    std::shared_ptr<B> shared_pointer = std::shared_ptr<B>(new D());//注意
    //我們new的是D
    shared_ptr = nullptr;


    return 0;
}

先上主要類的結構

template <>
class _Ptr_base<B> {
    //...

private:
    B* _Ptr;
    _Ref_count_base* _Rep;
}

template <>
class _Ref_count<D> : public _Ref_count_base {
public:
    explicit _Ref_count(D* _Px) : _Ref_count_base(), _Ptr(_Px) {}

private:
    // 最后析構函數會調用到這里
    virtual void _Destroy() noexcept override {
        delete _Ptr;
    }

    D* _Ptr;
};

template <>
class shared_ptr<B> : public _Ptr_base<B> {
    explicit shared_ptr(D* _Px) {
        _Ptr = _Px; // _Ptr 靜態類型是 B,但保存 new D() 的裸指針
        _Rep = new _Ref_count<D>(_Px) // _Ref_count 是 _Ref_count_base 的子類,該類保存了一個指向 D 的指針
    }
}

std::shared_ptr在構造函數中做了兩件事,初始化了兩個數據成員。因為要克服重重障礙,無法直接放在初始化列表中進行。這也就是引用計數與托管對象的分離實現案例。

1.std::shared_ptr中保存了裸指針(沒錯,它的靜態類型是B,但沒有virtual是不能正確析構的)

​ B* _Ptr; 也就是這行

2.以及一個_Ref_count結構,它借由模板構造函數竊取到了實際的指針信息,即D的信息(我們new的是D)

來了

// 析構函數調用鏈

~shared_ptr() noexcept {
    this->_Decref();
}

void _Ptr_base::_Decref() noexcept {
    if (_Rep) {
        _Rep->_Decref(); // 析構鏈轉入 _Ref_count_base,和父類已經沒關系了...
    }
}

void _Ref_count_base::_Decref() noexcept {
    if (_MT_DECR(_Uses) == 0) {
        _Destroy();
        //注意這里在前面我們看過,這里在基類也就是_Ref_count_base本類中是純虛函數
        所以會跳到下面那個
        _Decwref();
    }
}

virtual void _Ref_count::_Destroy() noexcept override {
    delete _Ptr; // 這是指向 D 的指針
}

template <>
class _Ptr_base<B> {
   //...

private:
   B* _Ptr;
   _Ref_count_base* _Rep;
}

最后銷毀的是子類的指針,即使沒有用virtual,但借由模板准確獲取到了靜態類型。

析構時居然完全不管share_ptr兩大成員之一的_Ptr,只析構引用計數_Rep,其實原因在構造函數中很清楚,

_Ptr的生命周期由引用計數托管。

另外,_Ref_count_base::_Decref()中還有一個_Decwref();,這個是判斷std::weak_ptr還在不在的。如果還在的話,雖然指針對象銷毀了,引用計數對象_Ref_count還在的,因為得讓std::weak_ptr查詢,如果連std::weak_ptr都沒有了,引用計數就把自己也銷毀。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM