什麼是智能指針?爲何要用智能指針?

什麼是智能指針?爲何要用智能指針?如何打破循環引用的問題?對於資源管理有什麼做用?

看到這些問題,內心就發毛。什麼是智能指針啊?爲何要用智能指針啊?循環引用又是什麼鬼?實現?我❌...
ios

首先咱們來看一下第一個問題,什麼是智能指針?

常見的智能指針有幾種,一種是共享指針shared_ptr,一種是獨享指針unique_ptr,一種是弱指針weak_ptr,一種是好久沒用過的auto_ptr(被unique_ptr替代了)。
智能指針也是指針,它也屬於指針的範疇,可是它比通常的指針智能,說明了是智能指針,確定就智能一點。就像如今的智能家居跟普通家居的區別。那麼它到底智能到什麼程度呢?咱們知道,動態內存,也就是咱們日常在堆中申請內存的時候,要是咱們用了指針,以下
程序員

T *p = new T();
delete p;
複製代碼

就須要進行管理,例如delete,delete和new是一對操做,delete[]和new[]他兩都是一對cp出現,來虐程序員這羣單身汪🐶。new這樣的操做就是爲對象分配空間並返回一個指向該對象的指針。因此返回了一個指針,那麼咱們就須要管理這個指針,delete就是接受一個動態對象的指針,銷燬該對象(調用析構函數,除了內置類型不作處理),並釋放與之關聯的內存。
你讓一個單身🐶去管理?這不鬧着玩嗎?<-_<-! 在動態內存的使用,比較容易出現問題,一旦忘記了delete或者在不正確的時間作了不正確的事情,或者在不正確的時間作了正確的事情,那也是不正確,或者在正確的時間作了不正確的事情,就容易發生內存泄漏。哪能怎麼辦?
因此 標準庫(記住是標準庫提供的),定義在頭文件<memory>,提供了智能指針,這爲了建設和諧美好社會,貢獻了很多。
智能指針行爲相似於常規的指針,這裏只是相似,由於智能指針會負責自動釋放所指向的對象。(其實就是讓cp滾遠點🐶🐶🐶)
並且指針並不能指出誰擁有了對象,不知道誰擁有全部權,可是智能指針卻擁有全部權。
後端

一我的的獨享,感受擁有了全世界(unique_ptr獨享全部權)

unique_ptr<string> p1(new string("hi,world")); // 必須採用直接初始化的形式初始化
    unique_ptr<string> p2(p1); // ❌ 不支持拷貝
    unique_ptr<string> p3;
    p3 = p2; // ❌ 不支持賦值
複製代碼

上面的代碼,是否是讓unique_ptr感受很適然,個人東西是個人,你不能留副本,僅此一件。他表示的是互斥全部權。通常的指針都支持拷貝賦值操做,可是這裏他就把拷貝構造函數和拷貝賦值運算符都delete了,根本不讓你拷貝。
安全

  • 一個unique_ptr「擁有」一個對象(它所指向的),某一個時刻,只能有一個unique_ptr指向一個給定的對象。當unique_ptr被銷燬,所指向的對象也被銷燬。
  • unique_ptr不能拷貝,不能賦值,能夠移動(p.release())
unique_ptr<string> p1(new string("hi"));
unique_ptr<string> p2(p1.release()); // 將p1置爲空,返回指針
cout << *p2 << endl;
unique_ptr<string> p3(new string("hello,world"));
p2.reset(p3.release()); // reset釋放了p2原來指向的內存 而後令p2指向p3所指向的對象,而後release()將p3置爲空
cout << *p3 << endl; // 輸出的都是hi
cout << *p1 << endl; // p1已經被釋放了,沒有了
複製代碼

那麼他那麼多限制,在標準庫的實現又是怎樣的呢?來看看下面這一段ide

template<typename T,typename D = default_delete<T> > // default_delete是一個無狀態類
class unique_ptr{
    public:
        using pointer = ptr;
        using element_type = T;
        using deleter_type = D;
        constexpr unique_ptr() noexcept;
        constexpr unique_ptr(nullptr_t) noexcept:unique_ptr(){} // 空指針類型
        explicit unique_ptr(pointer p) noexcept; // from pointer
        unique_ptr(pointer p,typename conditional<is_reference<D>::value,D,const D&> del) noexcept; // lvalue
        unique_ptr(pointer p,typename remove_reference<D>::type&& del) noexcept; // rvalue
        unique_ptr(unique_ptr&& x) noexcept;// 右值 移動構造函數
        template<class U,class E>
            unique_ptr(unique_ptr<U,E>&& x)noexcept; // 特例化
        template<class U>
            unique_ptr(auto_ptr<U>&& x)noexcept; // 不拋出異常
        Unique_ptr(const unique_ptr&) = delete; // 不容許拷貝

        unique_ptr& operator=(unique_ptr&& x) noexcept; // 移動賦值運算
        unique_ptr& operator=(nullptr_t) noexcept; // 空指針類型
        template<class U,class E>
            unique_ptr& operator=(unique_ptr<U,E>&& x)noexcept; // 強制類型轉換
        unique_ptr& operator=(const unique_ptr&) = delete; // 不容許賦值
};
複製代碼

爲了避免拋出異常,都設置了noexpect,以上包括了右值引用操做,左值引用操做,以及某一構造函數特例化操做。基本囊括並且也解釋了爲何不拷貝不賦值。
release()和reset() 這兩個函數都是將指針的全部權從一個(非const)unique_ptr轉移給另外一個unique_ptr。
reset()還能好一點,能夠釋放內存,可是release()就不行了,release()必須有 接盤俠,接了要麼能夠自動負責釋放,要麼負責手動釋放。
接下來咱們看看這兩個的實現方式
函數

void reset(pointer p = pointer()) noexcept;
// 這裏有一個默認值
pointer release() noexcept;
// 這裏返回一個值
複製代碼

release()是返回一個pointer,因此說它須要一個接盤俠。
ui

  • unique_ptr 保存一個指針,當他自身被銷燬時(例如線程控制流離開unique_ptr的做用域),使用關聯的釋放器(deleter)釋放所指向的對象
    釋放器又是什麼呢?當一個unique_ptr被銷燬,就會調用其本身的釋放器銷燬所擁有的對象。
deleter_type& get_deleter() noexcept;
const deleter_type& get_deleter() const noexcept;
複製代碼
  1. 局部變量的釋放器應該啥也不幹
  2. 內存池應該將對象歸還給內存池,是否銷燬它依賴於內存池如何定義。
  3. 默認調用delete釋放它所指向的對象
    管理釋放器又分爲在運行時綁定和在編譯時綁定,這兩個區別適用於區別shared_ptr和unique_ptr的,下面講完shared_ptr會統一講解,如今只要記住,unique_ptr管理釋放器時編譯時綁定的。
    那怎麼傳遞釋放器呢?咱們來看一個🌰
#include <memory>
#include <iostream>
#include <string>
using namespace std;

class Role{
    public:
        Role(const string &crole):role(crole){
            cout << role << endl;
        }
        ~Role(){
            cout << "delete" << endl;
        }
        void delRole(){
            cout << "delete Role outside" << endl;
        }
    private:
        string role;
};

void outdelRole(Role *r){
    r->delRole();
}

int main(){
    unique_ptr<Role,decltype(outdelRole)*> p1(new Role("trans"),outdelRole);
    return 0;
}
複製代碼

輸出trans delete Role outside
這個🌰,充分說明了,咱們能夠重載釋放器,若是是函數的釋放器,那麼他的參數類型必須是一個objT類型的指針,這樣纔有刪除的意義。decltype是通常用來指明類型的spa

unique_ptr<objT,delT>p(new objT,fcn); // fcn是delT類型對象
複製代碼

這樣你想怎麼刪,刪什麼就由你自個兒來定了。
也能夠這樣作線程

#include <iostream>
#include <memory>
using namespace std;
class state_deleter {  // a deleter class with state
  int count_;
public:
  state_deleter() : count_(0) {}
  template <class T> void operator()(T* p) {
    cout << "[deleted #" << ++count_ << "]\n";
    delete p;
  }
};

state_deleter del;
unique_ptr<int,state_deleter> alpha (new int);
unique_ptr<int,state_deleter> beta (new int,alpha.get_deleter());

// gamma and delta share the deleter "del" (deleter type is a reference!):
unique_ptr<int,state_deleter&> gamma (new int,del);
unique_ptr<int,state_deleter&> delta (new int,gamma.get_deleter());
複製代碼

再來看一段比較陷阱的代碼設計

unique_ptr<string> p1;
cout << *p1 << endl;
複製代碼

這段代碼表明p1是一個空指針,那這個空指針,沒有指向一個對象,那下面這一段呢?

unique_ptr<string> p1();
cout << *p1 << endl;
複製代碼

輸出的是1,爲何呢?由於unique_ptr<string> p1()聲明一個無參函數p1,返回的類型是unique_ptr類型的指針,因此要是*p1,那隻能是1,他是一個函數體

用途

  • 爲動態分配的內存提供異常安全
    unique_ptr能夠理解爲一個簡單的指針(指向一個對象)或一對指針(包含釋放器deleter的狀況)
  • 將動態分配內存的全部權傳遞給函數
  • 從函數返回動態分配的內存
  • 從容器中保存指針
    ⚠️這裏有一個get()的用法
pointer get() const noexcept;
複製代碼

get()是託管一個對象的指針或者空指針

unique_ptr<string> p1(new string("hello world"));
string *pstr = p1.get();
cout << *pstr << endl;
複製代碼

他與release()不一樣,它只是託管,get並是將pstr指向了p1指向的對象,可是並無釋放p1的內存,pstr並無獲取到這個智能指針的全部權,只是獲得了它的對象。p1仍是須要在某個時刻刪除託管數據pstr。
再來看一下解引用運算符

typename add_lvalue_reference<element_type>::type operator*() const;
複製代碼

做用支持指針操做唄

unique_ptr<string> p1(new string("hello world"));
cout << *p1 << endl;
複製代碼

再看看->運算符

pointer operator->()const noexcept;
複製代碼

支持指針行爲的操做

unique_ptr<C> foo (new C);
  unique_ptr<C> bar;

  foo->a = 10;
  foo->b = 20;

  bar = std::move(foo); // 支持右值移動操做 foo就釋放了
複製代碼

那好,咱們知道整個unique_ptr都會支持指針的行爲,那咱們看看它的特例化版本。什麼是特例化?就是對於特別的🌰進行特別的處理。不一樣的版本

template<class T,class D> class unique_ptr<T[],D>;
複製代碼
// 用於內置函數
unique_ptr<int[]> make_sequence(int n){
    unique_ptr<int[]> p{new int[n]};
    for(int i = 0;i<n;++i)
        p[i] = i;
    return p; // 返回局部對象
}
複製代碼

這裏固然要新增長獨一[]運算符的做用,也就是重載 []運算符。

element_type& operator[](size_t i)const;
複製代碼

不一樣擔憂匹配問題,咱們提供特例化版本只是幫編譯器作了匹配的工做而已。
那交換指針?交換也是移動操做呀!

template <class T,class D> void(unique_ptr<T,D>& x,unique_ptr<T,D>& y)noexpect;
複製代碼

交換兩方的全部權,你要個人,我要你的。固然這是非成員函數,也有成員函數的寫法

void swap(unique_ptr& x) noexcept;
複製代碼

就是a.swap(b)醬紫。

共享對象😁,你的對象我共享✨o✨(shared_ptr共享全部權)

既然講完了unique_ptr,那咱們就來說講這個讓社會更美好的shared_ptr,共享指針。
先來看看怎麼用

shared_ptr<string> p1; 
shared_ptr<list<int> > p2;
複製代碼

經過默認初始化,p1和p2都是空指針。固然這兩個操做,都沒有分配和使用動態內存。要怎麼作呢?咱們嘗試這樣。

shared_ptr<string> p1(new string("hehehe"));
cout << *p1 << endl;
複製代碼

也能夠試一下這樣

shared_ptr<int> clone(int p){
    return shared_ptr<int>(new int(p));
}
複製代碼

也能夠管理內置指針inum

int *inum = new int(42);
shared_ptr<int> p2(inum);
複製代碼

停🤚停🤚停🛑,先說明白,shared_ptr共享指針究竟是什麼?
shared_ptr表示共享全部權,和unique_ptr指針不一樣,shared_ptr能夠共享一個對象。當兩段代碼須要訪問同一個數據,但二者都沒有獨享全部權(負責銷燬對象)時,可使用shared_ptr。shared_ptr是一種計數指針,當計數(use_count)變爲0時釋放所指向的對象。
能夠理解爲包含兩個指針的結構,一個指針指向對象,另外一個指針指向計數器(use_count)。
而僅僅是由於當計數變爲0纔會銷燬所指向的對象,它的釋放器(deleter)與unique_ptr就不同,是一個非成員函數。可是是一個可調用對象,可調用對象後面我會專門去講,可是在這裏就要明白,shared_ptr的是釋放器是 運行時綁定的,而不是 編譯時就綁定的。而unique_ptr就是編譯時綁定的釋放器。默認的釋放器是delete,這個卻沒有變。(調用對象的析構函數並釋放自由存儲空間)
它的重點就在於使用計數上,那這個計數又是怎麼定義的呢?來看一段代碼。

shared_ptr<int> p3 = make_shared<int>(42);
cout << p3.use_count() << endl;
複製代碼

看吧,這裏的use_count()就是用來計數的,如今是1,就是這個對象引用了一次。

shared_ptr<int> p3 = make_shared<int>(42);
    auto r = p3;
    cout << p3.use_count() << endl;
複製代碼

這裏就是2了,這裏會怎樣,遞增p3的引用計數,那r呢?r的計數是多少?r是2啊,這裏就是說這個r也指向p3的對象了,那麼這個計數器確定是同樣的。要是r原來有指向的對象呢?那原來r的指向的對象的計數器也要遞減,也不影響其餘的指針。
因此其實區別就是,這些共享全部權的指針,都沒有權利把對象殺死,他把殺對象的事情外包了出去。(不忍心啊!😖)。
因此,這麼看來,由於有一個計數器,因此咱們能夠說,shared_ptr自動銷燬所管理的對象。也能夠說,shared_ptr自動釋放相關聯的內存。
能夠看一下這段代碼,來看看動態內存中的使用

#include <iostream>
#include <memory>
#include <string>
#include <initializer_list>
#include <vector>

using namespace std;

class StrBlob{
    public:
        typedef vector<string>::size_type size_type;
        StrBlob():data(make_shared<vector<string> >()){}
        StrBlob(initializer_list<string> il):data(make_shared<vector<string> >(il)){} // 使用參數列表初始化vector
        size_type size() const { return data->size();}
        bool empty() const { return data->empty();}
        void push_back(const string &t){return data->push_back(t);}
        void pop_back();
        string &front();
        string &back();
    private:
        shared_ptr<vector<string> > data; // 共享同一個數據?
        void check(size_type i,const string &msg) const;
};
複製代碼

當咱們拷貝,賦值或銷燬一個StrBlob對象的時候,這個shared_ptr的數據成員將會被拷貝、賦值和銷燬。那麼每一次都是安全的操做,自動釋放。由於計數器,因此安全。
因此其實也不復雜,就是但願咱們能夠用shared_ptr進行管理動態內存的資源。這裏我待會也會着重講(RAII)
ok,看完了在動態內存的資源管理,那咱們熟知的動態內存是怎樣的?是那對cp,就是new和delete。其實shared_ptr和new也能夠一塊兒用。

shared_ptr<double> p1; // shared_ptr 能夠指向一個double
shared_ptr<int> p2(new int(42)); // p2指向一個值42的int 直接初始化形式
複製代碼

咱們看構造函數

template<typename U>
    class shared_ptr{
        public:
            using element_type = U;
            constexpr shared_ptr() noexcept;
            constexpr shared_ptr(nullptr_t):shared_ptr(){} // 空對象
            template <class U> explicit shared_ptr(U* p); // 顯式構造 不存在隱式轉換
            template <class U,class D> shared_ptr(U* p,D del); // 添加釋放器
            template <class D> shared_ptr(nullptr_t p,D del); // 空指針的釋放器
            template <class U,class D, class Alloc> shared_ptr(U* p,D del,Alloc alloc); // 分配?
            template <class D,class Alloc> shared_ptr(nullptr_t p,D del,Alloc alloc);
            shared_ptr(const shared_ptr& x) noexcept;
            template<class U> shared_ptr(const shared_ptr<U>& x)noexcept;
            template<class U> explicit shared_ptr(const weak_ptr<U>& x);
            shared_ptr(shared_ptr&& x)(shared_ptr<U>&& x)noexcept; // 右值移動
            template <class U> shared_ptr(auto_ptr<U>&& x);
            template <class U,class D> shared_ptr(unique_ptr<U,D>&& x);// 得到獨享指針的全部權
            template <class U> shared_ptr(const shared_ptr<U>& x,element_type* p)noexcept;
    };
複製代碼

在構造函數中,接受指針參數的智能指針構造函數是explicit,就是顯式構造,而不是隱式轉換。

shared_ptr<int> clone(int p){
    return shared_ptr<int>(new int(p));
}
複製代碼

在primer中,建議 不要混合使用普通指針和智能指針,怎麼纔算是混合呢?咱們來看一下它給的🌰。

void process(shared_ptr<int> ptr){
    // 使用ptr
}// ptr離開做用域,被銷燬
複製代碼

在這個🌰中,ptr是值傳遞,你們都知道,值傳遞會增長拷貝,構造等成本,因此ptr計數值至少爲2,很公道,當process結束時,計數值不會變爲0。因此局部變量ptr被銷燬,ptr指向的內存也不會釋放。(因此說使用引用會減小增長引用計數)

void process(shared_ptr<int>& ptr){
    cout << ptr.use_count() << endl;
    cout << *ptr << endl;
}
複製代碼

當咱們使用值傳遞的時候,引用計數至少爲2,可是使用引用傳遞,引用計數就不會遞增

shared_ptr<int> p3 = make_shared<int>(42);
    cout << p3.use_count() << endl;
    // auto r = p3;
    // cout << r.use_count() << endl;
    process(p3);
    cout << p3.use_count() << endl;
複製代碼

使用引用計數,輸出始終如一。
看來這個🌰只能作引用和值傳遞的,好像和混合使用普通指針和智能指針沒啥搭邊啊!

int *x(new int(9));
    process(shared_ptr<int>(x));
    int j = *x;
    cout << j << endl;
複製代碼

上面的🌰咱們使用的是值傳遞。嗯。這個🌰說明什麼呢?可能不是很懂shared_ptr<int>(x)這種騷操做,咱們來看一下這樣會不會懂了一點

shared_ptr<int> ptr = shared_ptr<int>(new int(10));
複製代碼

懂了吧。

shared_ptr<T> p(q);
複製代碼

q是內置指針,p管理這個內置指針所指向的對象。q必須指向new分配的內存且可以轉換爲T*類型。
因此上上面的例子說明了,這兩個混合着用,臨時的shared_ptr會被銷燬,那所指向的內存也會被釋放。因此x估計還指向那個內存,可是,x已經不知不覺中變成空懸指針了。
其實當講一個shared_ptr綁定到一個普通指針時,咱們就將內存的管理責任交給了這位不知名的shared_ptr。因此,咱們就不能或者不該該再使用內置指針訪問shared_ptr所指向的內存。
primer也建議 不要使用get初始化另外一個智能指針或爲智能指針賦值。
get()函數上面也有簡略的介紹,它的做用是,它返回一個內置指針,指向智能指針管理的對象。它的設計是爲了在須要向不能使用智能指針的代碼傳遞一個內置指針。什麼意思?它只是一個託管指針。來看看這段代碼

shared_ptr<int> p(new int(42));
int *q = p.get();
{
    // 兩個獨立的shared_ptr指向相同的內存
    shared_ptr<int>(q);
    // 離開做用域就會釋放
}
int foo = *q; // 最後未定義
複製代碼

因此這裏解釋了不能用get()這樣的初始化另外一個智能指針,get()畢竟是託管,給你的都是已經有的,託管而已,給了你,你也是指向相同的內存。
固然,shared_ptr也可使用reset操做

string *inum = new string("hhh");
    shared_ptr<string> p5 = make_shared<string>("hi");
    p5.reset(inum);
複製代碼

可是他只能用於內置指針傳遞。
還能傳遞釋放器給shared_ptr p5.reset(inum,d);
那爲何shared_ptr沒有release成員? 沒有全部權唄。 講了那麼多,make_shared一直都像是被忽略了。

template <class T,class ... Args> shared_ptr<T> make_shared(Args&&... args);
複製代碼

這是它的源碼,他的用途就是製做shared_ptr,返回類型爲shared_ptr<T>的對象,該對象擁有並存儲指向它的指針(引用次數爲1)。
看看怎麼使用

auto baz =make_shared<pair<int,int> > (30,40);
... baz->first .. << baz->second
複製代碼

ok,因此,當咱們使用shared_ptr初始化的時候,最好最安全就是使用這個標準庫函數,而且使用new確定還要轉換啊,給予全部權,可是make_shared幫你將分配,安全都作好了,並且給你的就是返回的shared_ptr的類型對象,讓你的指針指向就好了。
推薦使用哦!
shared_ptr,其實就是一個指針,套上了釋放器,套上了計數器,拷貝的時候增長了引用,賦值也增長了引用,相應的也會有遞減了引用計數。咱們再來看另一種狀況

struct Node{
    shared_ptr<Node> pPre;
    shared_ptr<Node> pNext;
    int val;
};
void func(){
    shared_ptr<Node> p1(new Node());
    shared_ptr<Node> p2(new Node());
    cout << p1.use_count() << endl;
    cout <<p2.use_count() << endl;
    p1->pNext = p2;
    p2->pPre = p1;
    cout << p1.use_count() << endl;
    cout <<p2.use_count() << endl;
}
複製代碼

咱們看到,p1是2,p2也是2,他們互相拷貝引用啊!要想釋放p2就要先釋放p1,而要想釋放p1,就得釋放p2,這樣就是 循環引用了,最後p1和p2指向的內存空間永遠都沒法釋放掉。
那可咋辦咧,上面介紹的居然沒有一種能解決,不要慌,不要忙,靜靜在兩旁。
靜靜往下看看。

weak_ptr 讓靜靜繼續靜靜 該走的仍是讓你走

上面這個就是一個環,咱們怎樣打破這個環,讓內存釋放呢?使用weak_ptr。介紹一下weak_ptr,一種不控制所指向對象生存期的智能指針,指向由一個shared_ptr管理的對象。看來這也是共享全部權的樂趣,衆人幫,不像unique_ptr,一我的孤苦伶仃。
不控制是什麼意思?就是weak_ptr,不影響shared_ptr的引用計數。一旦shared_ptr被銷燬,那麼對象也會被銷燬,即便weak_ptr還指向這個對象,這個對象也會被銷燬。因此說,該走的仍是讓你走。
因此它也叫作"弱"共享全部權。
只引用,不計數,可是有沒有,要檢查expired()應運而生。
咱們來看一下他的構造以及使用

template <class T> class weak_ptr{
    public:
    constexpr weak_ptr() noexcept;
    weak_ptr(const weak_ptr& x) noexcept;
    template <class U> weak_ptr(const weak_ptr<U>& x) noexcept;
    template <class U> weak_ptr(const shared_ptr<U>& x) noexcept;
}
複製代碼

因此從構造函數能夠看出,這個weak_ptr,能夠本身構造,也能夠指向share_ptr,並且僅僅是引用。

shared_ptr<int> sp(new int(42));
weak_ptr<int> wp(sp);
cout << wp.use_count << endl;
複製代碼

那use_count呢?

long int use_count() const noexcept;
複製代碼

看到了嘛。它並不會改變引用計數。const
那expired是什麼? 它只是檢查use_count()是否是變爲0了,爲0返回false,不然返回true。

bool expired() const noexcept;
複製代碼

這是用來檢查一下這個指針所指向的對象是否被銷燬了。
因此這就致使對象可能就不存在,所以咱們不能使用weak_ptr直接訪問對象,何況weak_ptr也沒有*這個訪問運算符重載的過程,就須要調用別的函數,例如lock

shared_ptr<T> lock() const noexcept;
複製代碼

lock() 會檢查weak_ptr所指向的對象是否存在,若是存在就返回一個共享對象shared_ptr。

#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp1,sp2;
  std::weak_ptr<int> wp;
                                       // sharing group:
                                       // --------------
  sp1 = std::make_shared<int> (20);    // sp1
  wp = sp1;                            // sp1, wp

  sp2 = wp.lock();                     // sp1, wp, sp2
  sp1.reset();                         // wp, sp2

  sp1 = wp.lock();                     // sp1, wp, sp2

  std::cout << "*sp1: " << *sp1 << '\n';
  std::cout << "*sp2: " << *sp2 << '\n';

  return 0;
}
複製代碼

很清楚,都輸出20。一樣,reset就能置空一個weak_ptr
那麼爲何,weak_ptr能破環呢?咱們繼續來看下面這一段代碼

struct Node{
    weak_ptr<Node> pPre; // 區別⬅️⬅️⬅️
    weak_ptr<Node> pNext; // 區別⬅️⬅️⬅️
    int val;
    Node(){
        cout << "construct" << endl;
    }
    ~Node(){
        cout << "delete" <<endl;
    }
};
void func(){
    shared_ptr<Node> p1(new Node());
    shared_ptr<Node> p2(new Node());
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
    p1->pNext = p2;
    p2->pPre = p1;
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
}
複製代碼

這就打破了循環引用的環,由於每個shared_ptr都會將引用計數設爲1,那麼每次用都會遞增,因此要是不遞增,用原來的指向的對象不就解決了嘛。改一下結構就完美解決,並且還能調用了析構函數。

shared_ptr與unique_ptr釋放器 一動一靜顯神通

講完了weak_ptr,忽然感受,智能指針的發明確實偉大!單身🐶迷茫的時候容易犯的錯誤變得再也不容易。那麼,每次咱們都會發現,這兩個指針,會有一個釋放器。
unique_ptr版本

unique_ptr<T,D> up;
複製代碼

shared_ptr版本

shared_ptr<T> p(q,d);
複製代碼

無論大小寫的d都是delete,釋放器。向unique_ptr咱們以前介紹過,這是一個肯定的刪除器,在編譯時就已經決定了它的類型了。
unique_ptr

template<typename T,typename D = default_delete<T> > // default_delete是一個無狀態類
class unique_ptr{
    public:
        using pointer = ptr;
        using element_type = T;
        using deleter_type = D;
        ...
複製代碼

那shared_ptr咧

template<typename U>
class shared_ptr{
    public:
        using element_type = U;
        constexpr shared_ptr() noexcept;
        constexpr shared_ptr(nullptr_t):shared_ptr(){} // 空對象
        template <class U> explicit shared_ptr(U* p); // 顯式構造 不存在隱式轉換
        ...
複製代碼

看到這個template就明白,原來shared_ptr一直沒有固定類型的釋放器,雖然默認是delete,可是也可使用可調用對象,看看下面這個可調用對象的例子

#include <iostream>
#include <memory>

int main () {
   auto deleter = [](Node* p){
    cout << "[deleter called]\n"; 
    delete p;
    };
    // shared_ptr<int> foo (new int,deleter);
    // cout << "use_count: " << foo.use_count() << '\n';
    shared_ptr<Node> bar(new Node(),deleter);
  return 0;                        // [deleter called]
}
複製代碼

因此釋放器,不管是unique_ptr仍是shared_ptr都必須保存爲一個指針或一個封裝了指針的類。但咱們也能夠肯定,shared_ptr不是將釋放器直接保存爲一個成員,由於它的類型直到運行時才知道。
由於shared_ptr只有一個模版參數,而unique_ptr有兩個模版參數,因此在這個unique_ptr的工做方式,咱們能夠看出來,這個釋放器的類型是unique_ptr類型的一部分,因此釋放器能夠直接保存在unique_ptr對象中。
兩個釋放器都是對其保存的指針調用用戶提供提供的釋放器或執行delete
因此,總結一下,經過編譯時綁定釋放器,unique_ptr避免了間接調用釋放器的運行時開銷。
經過運行時綁定釋放器,shared_ptr使用戶重載釋放器更加方便。
因此這些都是以對象來管理資源的例子,一個一個shared_ptr,unique_ptr都在以對象的形式管理着資源,防止資源的泄露,動態內存不再用懼怕泄漏了。
額,那可調用對象又有哪些呢?怎麼用呢?爲何shared_ptr能夠這樣用可調用對象呢?
發佈了這篇文章,但願後端大牛們隨便噴,小弟定當改進😊。
另: 寫文不易,轉載請標明出處

未完待續...

相關文章
相關標籤/搜索