欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++11 shared_ptr 與 make_shared源碼剖析詳解

 更新時(shí)間:2021年09月17日 15:46:22   作者:私房菜  
這篇文章主要介紹了C++11 shared_ptr 與 make_shared的源碼剖析,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

0. 前言

所謂智能指針,可以從字面上理解為“智能”的指針。具體來講,智能指針和普通指針的用法是相似的,不同之處在于,智能指針可以在適當(dāng)時(shí)機(jī)自動(dòng)釋放分配的內(nèi)存。也就是說,使用智能指針可以很好地避免“忘記釋放內(nèi)存而導(dǎo)致內(nèi)存泄漏”問題出現(xiàn)。由此可見,C++ 也逐漸開始支持垃圾回收機(jī)制了,盡管目前支持程度還有限。

c++11 中發(fā)布了shared_ptr、unique_ptr、weak_ptr 用以資源的管理,都是定義在memory 這個(gè)頭文件中。

  • std::shared_ptr 允許多個(gè)shared_ptr 實(shí)例指向同一個(gè)對(duì)象,通過計(jì)數(shù)管理;
  • std::unique_ptr 是獨(dú)占對(duì)象;
  • weak_ptr 是輔助類,是一種弱引用,指向shared_ptr 所管理的對(duì)象。

1. 源碼分析

1.1 頭文件

#include <memory>

1.2 構(gòu)造

    constexpr shared_ptr() noexcept;
    template<class Y> explicit shared_ptr(Y* p);
    template<class Y, class D> shared_ptr(Y* p, D d);
    template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
    template <class D> shared_ptr(nullptr_t p, D d);
    template <class D, class A> shared_ptr(nullptr_t p, D d, A a);
    template<class Y> shared_ptr(const shared_ptr<Y>& r, T *p) noexcept;
    shared_ptr(const shared_ptr& r) noexcept;
    template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
    shared_ptr(shared_ptr&& r) noexcept;
    template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept;
    template<class Y> explicit shared_ptr(const weak_ptr<Y>& r);
    template<class Y> shared_ptr(auto_ptr<Y>&& r);          // removed in C++17
    template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
    shared_ptr(nullptr_t) : shared_ptr() { }

構(gòu)造函數(shù)比較多啊,抽一個(gè)看看源碼。

1.2.1 shared_ptr 的移動(dòng)構(gòu)造函數(shù)

template<class _Tp>
inline
shared_ptr<_Tp>::shared_ptr(shared_ptr&& __r) _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    __r.__ptr_ = 0;
    __r.__cntrl_ = 0;
}

大概知道,shared_ptr 中存放一個(gè)對(duì)象的指針__ptr_ 和用以計(jì)數(shù)的__cntrl_,這兩是shared_ptr 的私有成員變量:

template<class _Tp>
class shared_ptr {
    typedef _Tp element_type;
 
private:
    element_type*      __ptr_;
    __shared_weak_count* __cntrl_;
 
    ...
}

另外,移動(dòng)構(gòu)造函數(shù)因?yàn)橹皇莔ove,所以只是將舊的shared_ptr 轉(zhuǎn)移到新的里。

1.2.2 shared_ptr 的拷貝構(gòu)造函數(shù)

template<class _Tp>
inline
shared_ptr<_Tp>::shared_ptr(const shared_ptr& __r) _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    if (__cntrl_)
        __cntrl_->__add_shared();
}

與移動(dòng)構(gòu)造相同,shared_ptr 實(shí)例,需要從參數(shù)中獲得__ptr_ 和 __cntrl。

但是,與移動(dòng)構(gòu)造函數(shù)不同的是,拷貝構(gòu)造時(shí)增加對(duì)象計(jì)數(shù)的。

下面舉例shared_ptr 通常的創(chuàng)建方式:

std::shared_ptr<int> p1;                  //不傳入任何實(shí)參
std::shared_ptr<int> p2(nullptr);         //傳入空指針 nullptr
std::shared_ptr<int> p3(new int(10));     //指定指針為參數(shù)
std::shared_ptr<int> p4(p3);              //或者 std::shared_ptr<int> p4 = p3;
std::shared_ptr<int> p5(std::move(p4));   //或者 std::shared_ptr<int> p5 = std::move(p4);

1.3 賦值重載

    shared_ptr& operator=(const shared_ptr& r) noexcept;
    template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;
    shared_ptr& operator=(shared_ptr&& r) noexcept;
    template<class Y> shared_ptr& operator=(shared_ptr<Y>&& r);
    template<class Y> shared_ptr& operator=(auto_ptr<Y>&& r); // removed in C++17
    template <class Y, class D> shared_ptr& operator=(unique_ptr<Y, D>&& r);

1.4 修改的接口

    void swap(shared_ptr& r) noexcept;
    void reset() noexcept;
    template<class Y> void reset(Y* p);
    template<class Y, class D> void reset(Y* p, D d);
    template<class Y, class D, class A> void reset(Y* p, D d, A a);

 reset 基本上是對(duì)應(yīng)構(gòu)造

1.5 獲取

    T* get() const noexcept;
    T& operator*() const noexcept;
    T* operator->() const noexcept;
    long use_count() const noexcept;
    bool unique() const noexcept;
    explicit operator bool() const noexcept;

對(duì)于shared_ptr 的成員函數(shù)總結(jié)如下:

成員方法名

功 能

operator=()

重載賦值號(hào),使得同一類型的 shared_ptr 智能指針可以相互賦值。

operator*()

重載 * 號(hào),獲取當(dāng)前 shared_ptr 智能指針對(duì)象指向的數(shù)據(jù)。

operator->()

重載 -> 號(hào),當(dāng)智能指針指向的數(shù)據(jù)類型為自定義的結(jié)構(gòu)體時(shí),通過 -> 運(yùn)算符可以獲取其內(nèi)部的指定成員。

swap()

交換 2 個(gè)相同類型 shared_ptr 智能指針的內(nèi)容。

reset()

當(dāng)函數(shù)沒有實(shí)參時(shí),該函數(shù)會(huì)使當(dāng)前 shared_ptr 所指堆內(nèi)存的引用計(jì)數(shù)減 1,同時(shí)將當(dāng)前對(duì)象重置為一個(gè)空指針;當(dāng)為函數(shù)傳遞一個(gè)新申請(qǐng)的堆內(nèi)存時(shí),則調(diào)用該函數(shù)的 shared_ptr 對(duì)象會(huì)獲得該存儲(chǔ)空間的所有權(quán),并且引用計(jì)數(shù)的初始值為 1。

get()

獲得 shared_ptr 對(duì)象內(nèi)部包含的普通指針。

use_count()

返回同當(dāng)前 shared_ptr 對(duì)象(包括它)指向相同的所有 shared_ptr 對(duì)象的數(shù)量。

unique()

判斷當(dāng)前 shared_ptr 對(duì)象指向的堆內(nèi)存,是否不再有其它 shared_ptr 對(duì)象再指向它。

operator bool()

判斷當(dāng)前 shared_ptr 對(duì)象是否為空智能指針,如果是空指針,返回 false;反之,返回 true。

除了上面的成員函數(shù)外,C++11 標(biāo)準(zhǔn)還支持同一類型的 shared_ptr 對(duì)象,或者 shared_ptr 和 nullptr 之間,進(jìn)行 ==,!=,,>= 運(yùn)算。

注意:

  • shared_ptr用以共享一個(gè)對(duì)象的所有權(quán),通過計(jì)數(shù)確認(rèn)自動(dòng)回收;
  • shared_ptr 共享的對(duì)象所有權(quán),通過存儲(chǔ)對(duì)象的指針,并通過get() 獲取存儲(chǔ)的對(duì)象的指針;
  • 可能在多個(gè)線程中同時(shí)用不同的shared_ptr實(shí)例,而這些實(shí)例可能指向同一個(gè)對(duì)象。多線程在沒有同步的情況訪問同一個(gè)shared_ptr 實(shí)例,并調(diào)用其non-const 成員函數(shù)時(shí),有可能存在數(shù)據(jù)競(jìng)爭(zhēng)。可以使用std::atomic* 函數(shù)保護(hù)數(shù)據(jù)競(jìng)爭(zhēng);

 2. make_shared

template<class T, class... Args>
    shared_ptr<T> make_shared(Args&&... args);

c++11 中針對(duì)shared_ptr 還提供了make_shared 這個(gè)外部函數(shù),用以創(chuàng)建一個(gè)shared_ptr 實(shí)例。

c++ 建議盡可能使用make_shared 創(chuàng)建shared_ptr 實(shí)例。

下面來說明下make_shared 的優(yōu)缺點(diǎn)。

2.1 make_shared 優(yōu)點(diǎn)

2.1.1 效率高

shared_ptr 需要維護(hù)引用計(jì)數(shù)的信息:

  • 強(qiáng)引用, 用來記錄當(dāng)前有多少個(gè)存活的 shared_ptrs 正持有該對(duì)象. 共享的對(duì)象會(huì)在最后一個(gè)強(qiáng)引用離開的時(shí)候銷毀( 也可能釋放).
  • 弱引用, 用來記錄當(dāng)前有多少個(gè)正在觀察該對(duì)象的 weak_ptrs. 當(dāng)最后一個(gè)弱引用離開的時(shí)候, 共享的內(nèi)部信息控制塊會(huì)被銷毀和釋放 (共享的對(duì)象也會(huì)被釋放, 如果還沒有釋放的話).

如果你通過使用原始的 new 表達(dá)式分配對(duì)象, 然后傳遞給 shared_ptr (也就是使用 shared_ptr 的構(gòu)造函數(shù)) 的話, shared_ptr 的實(shí)現(xiàn)沒有辦法選擇, 而只能單獨(dú)的分配控制塊:

auto p = new widget();
shared_ptr sp1{ p }, sp2{ sp1 };

如果選擇使用 make_shared 的話, 情況就會(huì)變成下面這樣:

auto sp1 = make_shared(), sp2{ sp1 };

內(nèi)存分配的動(dòng)作, 可以一次性完成. 這減少了內(nèi)存分配的次數(shù), 而內(nèi)存分配是代價(jià)很高的操作。

2.1.2 異常安全

void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));

C++ 是不保證參數(shù)求值順序, 以及內(nèi)部表達(dá)式的求值順序的, 所以可能的執(zhí)行順序如下:

  • new Lhs(“foo”))
  • new Rhs(“bar”))
  • std::shared_ptr
  • std::shared_ptr

 好了, 現(xiàn)在我們假設(shè)在第 2 步的時(shí)候, 拋出了一個(gè)異常 (比如 out of memory, 總之, Rhs 的構(gòu)造函數(shù)異常了), 那么第一步申請(qǐng)的 Lhs 對(duì)象內(nèi)存泄露了. 這個(gè)問題的核心在于, shared_ptr 沒有立即獲得裸指針

我們可以用如下方式來修復(fù)這個(gè)問題.

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

當(dāng)然, 推薦的做法是使用 std::make_shared 來代替:

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

2.2 make_shared缺點(diǎn) 

構(gòu)造函數(shù)是保護(hù)或私有時(shí),無法使用 make_shared

make_shared 雖好, 但也存在一些問題, 比如, 當(dāng)我想要?jiǎng)?chuàng)建的對(duì)象沒有公有的構(gòu)造函數(shù)時(shí), make_shared 就無法使用了, 當(dāng)然我們可以使用一些小技巧來解決這個(gè)問題, 比如這里 How do I call ::std::make_shared on a class with only protected or private constructors?

對(duì)象的內(nèi)存可能無法及時(shí)回收

make_shared 只分配一次內(nèi)存, 這看起來很好. 減少了內(nèi)存分配的開銷. 問題來了, weak_ptr 會(huì)保持控制塊(強(qiáng)引用, 以及弱引用的信息)的生命周期, 而因此連帶著保持了對(duì)象分配的內(nèi)存, 只有最后一個(gè) weak_ptr 離開作用域時(shí), 內(nèi)存才會(huì)被釋放. 原本強(qiáng)引用減為 0 時(shí)就可以釋放的內(nèi)存, 現(xiàn)在變?yōu)榱藦?qiáng)引用, 若引用都減為 0 時(shí)才能釋放, 意外的延遲了內(nèi)存釋放的時(shí)間. 這對(duì)于內(nèi)存要求高的場(chǎng)景來說, 是一個(gè)需要注意的問題

無法像shared_ptr 的構(gòu)造那樣添加一個(gè)deleter

3. 舉例

#include <iostream>
#include <memory>
using namespace std;
 
int main()
{
    //構(gòu)建 2 個(gè)智能指針
    std::shared_ptr<int> p1(new int(10));
    std::shared_ptr<int> p2(p1);
    //輸出 p2 指向的數(shù)據(jù)
    cout << *p2 << endl;
    p1.reset();//引用計(jì)數(shù)減 1,p1為空指針
    if (p1) {
        cout << "p1 不為空" << endl;
    }
    else {
        cout << "p1 為空" << endl;
    }
    //以上操作,并不會(huì)影響 p2
    cout << *p2 << endl;
    //判斷當(dāng)前和 p2 同指向的智能指針有多少個(gè)
    cout << p2.use_count() << endl;
    return 0;
}

運(yùn)行結(jié)果:

10
p1 為空
10
1

參考:

C++11 std::shared_ptr總結(jié)與使用示例代碼詳解

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • C++深入淺出探索數(shù)據(jù)結(jié)構(gòu)的原理

    C++深入淺出探索數(shù)據(jù)結(jié)構(gòu)的原理

    C++的數(shù)據(jù)結(jié)構(gòu)很多,很復(fù)雜,所以本文將通過示例帶大家深入了解一下C++中的數(shù)據(jù)結(jié)構(gòu)與算法。文中的示例代碼講解詳細(xì),感興趣的可以了解一下
    2022-05-05
  • C語言二維數(shù)組中的查找的實(shí)例

    C語言二維數(shù)組中的查找的實(shí)例

    這篇文章主要介紹了C語言二維數(shù)組中的查找的實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-07-07
  • C C++算法題解LeetCode1408數(shù)組中的字符串匹配

    C C++算法題解LeetCode1408數(shù)組中的字符串匹配

    這篇文章主要為大家介紹了C C++算法題解LeetCode1408數(shù)組中的字符串匹配示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • C++中的多態(tài)詳談

    C++中的多態(tài)詳談

    多態(tài)通俗來說就是多種形態(tài),本文通過實(shí)例代碼給大家介紹C++中的多態(tài)定義及實(shí)現(xiàn),通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2021-05-05
  • C/C++格式化日志庫實(shí)現(xiàn)代碼

    C/C++格式化日志庫實(shí)現(xiàn)代碼

    這篇文章主要介紹了C/C++格式化日志庫實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2019-04-04
  • C++ Boost Bimap示例詳細(xì)講解

    C++ Boost Bimap示例詳細(xì)講解

    Boost是為C++語言標(biāo)準(zhǔn)庫提供擴(kuò)展的一些C++程序庫的總稱。Boost庫是一個(gè)可移植、提供源代碼的C++庫,作為標(biāo)準(zhǔn)庫的后備,是C++標(biāo)準(zhǔn)化進(jìn)程的開發(fā)引擎之一,是為C++語言標(biāo)準(zhǔn)庫提供擴(kuò)展的一些C++程序庫的總稱
    2022-11-11
  • VS編譯出現(xiàn)MSB3073命令的解決方案

    VS編譯出現(xiàn)MSB3073命令的解決方案

    =error MSB3073是Visual Studio編譯器報(bào)出的錯(cuò)誤,本文主要介紹了VS編譯出現(xiàn)MSB3073命令的解決方案,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • C語言實(shí)現(xiàn)模擬USB對(duì)8bit數(shù)據(jù)的NRZI編碼輸出

    C語言實(shí)現(xiàn)模擬USB對(duì)8bit數(shù)據(jù)的NRZI編碼輸出

    今天小編就為大家分享一篇關(guān)于C語言實(shí)現(xiàn)模擬USB對(duì)8bit數(shù)據(jù)的NRZI編碼輸出,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • C++20中的結(jié)構(gòu)化綁定類型示例詳解

    C++20中的結(jié)構(gòu)化綁定類型示例詳解

    這篇文章主要為大家介紹了C++20中的結(jié)構(gòu)化綁定類型示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • c++中的bind使用方法

    c++中的bind使用方法

    bind是這樣一種機(jī)制,它可以預(yù)先把指定可調(diào)用實(shí)體的某些參數(shù)綁定到已有的變量,產(chǎn)生一個(gè)新的可調(diào)用實(shí)體,這種機(jī)制在回調(diào)函數(shù)的使用過程中也頗為有用。接下來通過本文給大家介紹c++中的bind使用方法,感興趣的朋友一起看看吧
    2022-01-01

最新評(píng)論