日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

智能指针 的理解

發布時間:2024/3/26 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 智能指针 的理解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

智能指針主要解決的問題

1、內存泄露:內存手動釋放,使用智能指針可以自動釋放malloc free ;new delete 2、共享所有權指針的傳播和釋放,比如多線程使用同一個對象時析構問題

C++里面的四種智能指針:auto_ptr,shared_ptr,unique_ptr,weak_ptr其中后三個是C++支持,并且第一個已經被棄用

  • unique_ptr 獨占對象的所有權,由于沒有引用計數,因此性能比較好
  • shared_ptr 共享對象的所有權,但性能比較差
  • weak_ptr 配合shared_ptr ,解決循環引用問題。

shared_ptr內存模型


shared_ptr 內部包含兩個指針,一個指向對象,另一個指向控制塊(control block),控制塊中包含一個引用計數(reference count),一個弱計數(weak count)和其它一些數據。

智能指針使用場景案例

使用智能指針可以自動釋放占用的空間

shared_ptr<Buffer> buf = make_shared<Buffer>("auto free memory"); // buf 對象分配在堆上,但能自動釋放 // 對比 Buffer *buf = new Buffer("auto free memory");//buf對象分配在堆上,但需要手動delete釋放

共享所有權指針的傳播和釋放

shared_ptr 共享的智能指針

std::shared_ptr 使用引用計數器,每一個shared_ptr的拷貝都指向相同的內存。再最后一個shared_ptr析構的時候,內存才會被釋放。
shared_ptr 共享被管理對象,同一時刻可以有多個shared_ptr 擁有對象的所有權,當最后一個shared_ptr對象銷毀時,被管理對象自動銷毀。

簡單說,shared_ptr 實現包含了兩部分,

  • 一個指向堆上創建的對象的裸指針,raw_ptr
  • 一個指向內部隱藏的、共享的管理對象。share_count_obj

shared_ptr 的基本用法和常用函數

  • s.get():返回shared_ptr中保存的裸指針;
  • s.reset():重置shared_ptr;
  • reset() :不帶參數時,若智能指針s是唯一指向該對象的指針,則釋放,并置空。若智能指針P不是唯一指向該對象的指針,則引用計數減少1,同時將P置空。
  • reset():帶參數時,若智能指針s是唯一指向對象的指針,則釋放并指向新的對象。若P不是唯一的指針,則只減少引用計數,并指向新的對象。
auto s = make_shared<int>(100); s.reset(new int(200));

s.use_count():返回shared_ptr的強引用計數;
s.unique():若use_count為1,返回true,否則返回false。

初始化make_shared/reset

通過構造函數、std::shared_ptr輔助函數和reset方法來初始化shared_ptr,代碼如下:

std::shared_ptr<int> p1(new int(1)); std::shared_ptr<int> p2 = p1; std::shared_ptr<int> p3; if(p3) {cout << "p3 is not null"; }

用make_shared來構建智能指針,因為make_shared比較高效。

auto sp1 = make_shared<int>(100); //或 shared_ptr<int> sp1 = make_shared<int>(100); //相當于 shared_ptr<int> sp1(new int(100));

不能將一個原始指針直接賦值給一個智能指針,例如:

std::shared_ptr<int> p = new int(1);

shared_ptr 不能通過“直接將原始這種賦值”來初始化,需要通過構造函數和輔助方法來初始化

  • 對于一個未初始化的智能指針,可以通過reset方法來初始化;
  • 當智能指針有值的時候調用reset會引起引用計數減1

另外智能指針可以通過重載的bool類型操作符來判斷。

int main {std::shared_ptr<int> p1;p1.reset(new int(1));std::shared_ptr<int> p2 = p1;// p2.use_count() = 2cout << "p2.use_count() = " << p2.use_count() << endl;p1.reset();cout << "p1.reset()\n";// p2.use_count() = 1cout << "p2.use_count()= " << p2.use_count() << endl;if (!p1) {cout << "p1 is empty\n"; // 執行了}if (!p2) {cout << "p2 is empty\n";}p2.reset();cout << "p2.reset()\n";// p2.use_count() = 0cout << "p2.use_count()= " << p2.use_count() << endl;if (!p2) {cout << "p2 is empty\n"; // 執行了} }

獲取原始指針get

當需要獲取原始指針時,可以通過get方法來返回原始指針:

std::shared_ptr<int> ptr(new int(1)); int *p = ptr.get(); // 獲取

盡量不要使用p.get()的返回值,如果不知道其危險性則永遠不要調用get() 函數。

指定刪除器

如果用shared_ptr 管理非new對象或是沒有析構函數的類時,應當為其傳遞合適的刪除器。

#include <iostream> #include <memory> using namespace std; void DeleteIntPtr(int *p) {cout <<"call DeleteIntPtr" << endl;delete p; } int main() {std::shared_ptr<int> p(new int(1),DeleteIntPtr);return 0; }

當p的引用計數為0時,自動調用刪除器DeleteIntPtr來釋放對象的內存。刪除器可以是一個lambda表達式,如:

std::shared_ptr<int> p(new int(1),[](int *p) {cout << "call lambda delete p " << endl;delete p; })

當用shared_ptr管理動態數組時,需要指定刪除器,因為shared_ptr的默認刪除器不支持數組對象,代碼如:

std::shared_ptr<int> p3(new int[10],[](int *p) {delete [] p;})

使用shared_ptr 要注意的問題

不要用一個原始指針初始化多個shared_ptr

int *ptr = new int; shared_ptr<int> p1(ptr); shared_ptr<int> p2(ptr); // 邏輯錯誤

不要在函數實參中創建shared_ptr

func(shared_ptr<int>(new int),g()); // 有缺陷

因為c++的函數參數的計算順序在不同的編譯器不同的約定下可能不一樣的。 一般是從右到左,但也可能從左到右,所以,可能的過程是先new int,然后調用g(),如果恰好g()發生異常,而shared_ptr還沒有創建,則int內存就泄露了, 正確寫法如下:

shared_ptr<int> p(new int); func(p,g());

通過shared_from_this() 返回this指針

不要將this指針作為shared_ptr 返回出來,因為this指針本質上是一個裸指針,因此,這樣可能會導致重復析構,如:

#include <iostream> #include <memory> using namespace std; class A { public: shared_ptr<A> GetSelf() { return shared_ptr<A>(this); // 不要這么做 } ~A() { cout << "Destructor A" << endl; } }; int main() { shared_ptr<A> sp1(new A); shared_ptr<A> sp2 = sp1->GetSelf(); return 0; }

運行后調用了兩次析構函數。

在例子中,由于用同一個指針(this)構造了兩個智能指針sp1和sp2,而他們之間是沒有任何關系的,在離開作用域之后this將會被構造的兩個智能指針各自析構,導致重復析構的錯誤。
正確返回this和shared_ptr的做法是: 讓目標類通過std::enable_shared_from_this類,然后使用基類的成員函數 shared_from_this()來返回this的shared_ptr,如下:

#include <iostream> #include <memory> using namespace std; class A: public std::enable_shared_from_this<A> { public: shared_ptr<A>GetSelf() { return shared_from_this(); // } ~A() { cout << "Destructor A" << endl; } }; int main() { shared_ptr<A> sp1(new A); shared_ptr<A> sp2 = sp1->GetSelf(); // ok return 0; }

在weak_ptr章節我們繼續講解使用shared_from_this() 的原因。

避免循環引用

循環引用會導致內存泄露,如:

#include <iostream> #include <memory>using namespace std;class A; class B; class A { public:std::shared_ptr<B> bptr;~A() {cout << "A is deleted" << endl;} }; class B { public:std::shared_ptr<A> aptr;~B() {cout << "B is deleted" << endl;} }; int main() {{std::shared_ptr<A> ap(new A);std::shared_ptr<A> bp(new B);ap->bptr = bp;bp->aptr = ap;}cout << "main leave" << endl; // 循環引用導致ap bp 退出了作用域都沒有析構return 0; }

循環引用導致ap和bp的應用計數為2,在離開作用域之后,ap和bp的引用計數減為1,并不會減0,導致兩個指針都不會被析構,產生內存泄露。
解決的辦法是把A和B任何一個成員變量改為weak_ptr,具體方法見weak_ptr

unique_ptr 獨占的智能指針

  • unique_ptr 是一個獨占型的智能指針,不能將其賦值給另一個unique_ptr
  • unique_ptr 可以指向一個數組
  • unique_ptr 需要確定刪除器的類型
  • unique_ptr是一個獨占型的智能指針,它不允許其他的智能指針共享其內部的指針,不允許通過賦值將一個unique_ptr賦值給另一個unique_ptr,如:

    unique_ptr<T> my_ptr(new T); unique_ptr<T> my_other_ptr = my_ptr; // 報錯,不能復制

    unique_ptr不允許復制,但可以通過函數返回給其它unique_ptr,還可以通過std::move來轉移到其他的unique_ptr,這樣它本身就不再擁有原來指針的所有權了,如:

    unique_ptr<T> my_ptr(new T) ; // 正確 unique_ptr<T> my_other_ptr = std::move(my_ptr); // 正確 unique_ptr<T> ptr = my_ptr; //報錯,不能復制

    std::make_shared 是c++的一部分,但std::make_unique不是。

    auto upw1(std::make_unique<widget>()); std::unique_ptr<widget> upw2(new widget);

    除了unique_ptr 的獨占性,unique_ptr和shared_ptr 還有一些區別,比如:

    • unique_ptr可以指向一個數組,代碼如下:
    std::unique_ptr<int []> ptr(new int[10]); ptr[9] = 9; std::shared_ptr<int []> ptr2(new int[10]); // 這個是不合法的
    • unique_ptr 指定刪除器和shared_ptr 有區別
    std::shared_ptr<int> ptr3(new int(1),[](int *p){delete p;}) // 正確 std::unique_ptr<int> ptr4(new int(9),[](int *p) {delete p;})// 錯誤
    • unique_ptr需要確定刪除器的類型,所以不能像shared_ptr 那樣直接指定刪除器,可以這樣寫:
    std::unique_ptr<int,void(*)(int *)>ptr5(new int(1),[](int *p){delete p;}) // 正確

    關于shared_ptr 和unique_ptr 的使用場景是要根據實際應用需求來選擇。

    如果希望只有一個智能指針管理資源或者管理數組就用unique_ptr,如果希望多個智能指針管理同一個資源就用shared_ptr。

    weak_ptr 弱引用的智能指針

    weak_ptr 是一種 不控制對象生命周期的智能指針,它指向一個shared_ptr 管理的對象,進行該對象的內存管理的那個強引用的shared_ptr,weak_ptr只提供了對管理對象的一種訪問手段。

    weak_ptr 設計的目的是為配合shared_ptr 而引入的一種智能指針來協助shared_ptr 工作,它只可以從一個shared_ptr或另一個weak_ptr對象構造,它的構造和析構不會引起引用計數的增加或減少。

    weak_ptr 的基本用法

  • 通過use_count() 方法獲取當前觀察資源的引用計數,如下:
  • shared_ptr<int> sp(new int(10)); weak_ptr<int> wp(sp); cout << wp.use_count() << endl; // 結果輸出1
  • 通過expired() 方法判斷所觀察資源是否已經釋放,如:
  • shared_ptr<int> sp(new int(10)); weak_ptr<int> wp(sp); if(wp.expired()) {cout << "weak_ptr 無效資源, 資源已經被釋放"; }else {cout << "weak_ptr 有效"; }
  • 通過lock方法獲取監視的weak_ptr,如:
  • std::weak_ptr<int> gw1; void f() {auto spt = gw1.lock();if (gw1.expired()) {cout << "gw無效,資源已釋放";}else {cout << "gw有效, *spt = " << *spt << endl;} }int main() {{auto sp = std::make_shared<int>(42);gw1 = sp;f();}f();return 0; }

    結果輸出:

    gw有效, *spt = 42 gw無效,資源已釋放

    weak_ptr 返回this指針

    shared_ptr章節中提到不能直接將this指針返回shared_ptr,需要通過派生
    std::enable_shared_from_this類,并通過其方法shared_from_this來返回指針,原因是
    std::enable_shared_from_this類中有一個weak_ptr,這個weak_ptr用來觀察this智能指針,調用
    shared_from_this()方法是,會調用內部這個weak_ptr的lock()方法,將所觀察的shared_ptr返回,再看
    前面的范例:

    #include <iostream> #include <memory> using namespace std; class A: public std::enable_shared_from_this<A> { public:shared_ptr<A>GetSelf(){return shared_from_this(); //}~A(){cout << "Destructor A" << endl;} }; int main() {shared_ptr<A> sp1(new A);shared_ptr<A> sp2 = sp1->GetSelf(); // okreturn 0; }

    輸出結果如下:
    Destructor A

    在外面創建A對象的智能指針和通過對象返回this的智能指針都是安全的,因為shared_from_this()是內
    部的weak_ptr調用lock()方法之后返回的智能指針,在離開作用域之后,spy的引用計數減為0,A對象會
    被析構,不會出現A對象被析構兩次的問題。

    需要注意的是,獲取自身智能指針的函數盡在shared_ptr的構造函數被調用之后才能使用,因為
    enable_shared_from_this內部的weak_ptr只有通過shared_ptr才能構造。

    weak_ptr 解決循環引用問題

    在shared_ptr章節提到智能指針循環引用的問題,因為智能指針的循環引用會導致內存泄漏,可以通過
    weak_ptr解決該問題,只要將A或B的任意一個成員變量改為weak_ptr

    #include <iostream> #include <memory> using namespace std; class A; class B; class A { public:std::weak_ptr<B> bptr; // 修改為weak_ptr~A() {cout << "A is deleted" << endl;} }; class B { public:std::shared_ptr<A> aptr;~B() {cout << "B is deleted" << endl;} }; int main() {{std::shared_ptr<A> ap(new A);std::shared_ptr<B> bp(new B);ap->bptr = bp;bp->aptr = ap;}cout<< "main leave" << endl;return 0; }

    這樣在對B的成員賦值時,即執行bp->aptr=ap;時,由于aptr是weak_ptr,它并不會增加引用計數,所
    以ap的引用計數仍然會是1,在離開作用域之后,ap的引用計數為減為0,A指針會被析構,析構后其內
    部的bptr的引用計數會被減為1,然后在離開作用域后bp引用計數又從1減為0,B對象也被析構,不會發生內存泄漏。

    weak_ptr 使用注意事件

    weak_ptr 在使用前需要檢查合法性。

    weak_ptr<int> wp; { shared_ptr<int> sp(new int(1)); //sp.use_count()==1 wp = sp; //wp不會改變引用計數,所以sp.use_count()==1 shared_ptr<int> sp_ok = wp.lock(); //wp沒有重載->操作符。只能這樣取所指向的對象 } shared_ptr<int> sp_null = wp.lock(); //sp_null .use_count()==0

    因為上述代碼中sp和sp_ok離開了作用域,其容納的K對象已經被釋放了。
    得到了一個容納NULL指針的sp_null對象。在使用wp前需要調用wp.expired()函數判斷一下。
    因為wp還仍舊存在,雖然引用計數等于0,仍有某處“全局”性的存儲塊保存著這個計數信息。直到最后
    一個weak_ptr對象被析構,這塊“堆”存儲塊才能被回收。否則weak_ptr無法直到自己所容納的那個指針
    資源的當前狀態。

    如果shared_ptr sp_ok和weak_ptr wp;屬于同一個作用域呢?如下所示:

    weak_ptr<int> wp;shared_ptr<int> sp_ok;{shared_ptr<int> sp(new int(1)); //sp.use_count()==1wp = sp; //wp不會改變引用計數,所以sp.use_count()==1sp_ok = wp.lock(); //wp沒有重載->操作符。只能這樣取所指向的對象}if(wp.expired()) {cout << "shared_ptr is destroy" << endl;} else {cout << "shared_ptr no destroy" << endl;}

    智能指針安全性問題

    引用計數本身是安全的,至于智能指針是否安全需要結合實際使用分情況討論:

    情況1:多線程代碼操作的是同一個shared_ptr的對象,此時是不安全的。
    比如std::thread的回調函數,是一個lambda表達式,其中引用捕獲了一個shared_ptr

    情況2:多線程代碼操作的不是同一個shared_ptr的對象
    這里指的是管理的數據是同一份,而shared_ptr不是同一個對象。比如多線程回調的lambda的是按值捕
    獲的對象。

    void fn(shared_ptr<A>sp) { ... } .. std::thread td(fn, sp1);

    這時候每個線程內看到的sp,他們所管理的是同一份數據,用的是同一個引用計數。但是各自是不同的
    對象,當發生多線程中修改sp指向的操作的時候,是不會出現非預期的異常行為的。
    也就是說,如下操作是安全的。

    void fn(shared_ptr<A>sp) { ...if(..){sp = other_sp;} else {sp = other_sp2;} }

    需要注意:所管理數據的線程安全性問題。顯而易見,所管理的對象必然不是線程安全的,必然 sp1、
    sp2、sp3智能指針實際都是指向對象A, 三個線程同時操作對象A,那對象的數據安全必然是需要對象
    A自己去保證。

    總結

    以上是生活随笔為你收集整理的智能指针 的理解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。