【C/C++基础进阶系列】C/C++ STL -- 智能指针
【C/C++基礎進階系列】C/C++ STL -- 智能指針
【1】概述
裸指針,直接 new 一個對應并返回的指向該對象的指針;
智能指針,封裝裸指針使得可以自動釋放所指向的對象,C++ 標準庫中提供了 4 中智能指針,即 std::auto_ptr (目前已被廢棄)、std::unique_ptr、std::shared_ptr、std::weak_ptr;
- std::shared_ptr 共享式指針,多個指針指向同一個對象,當最后一個指針被銷毀時,該對象將會被釋放;
- std::weak_ptr 輔助?std::shared_ptr 用于監視其指向的對象是否存在;
- std::unique_ptr 獨占式指針,同一個時間內只有一個指針能夠指向該對象,同時,該對象的所有權可以移交出去;
【2】std::shared_ptr
- std::shared_ptr 工作機制
使用引用計數,每一個?std::shared_ptr 指針都指向相同的對象 (內存),只有當最后一個指向該對象的?std::shared_ptr 指針不再指向該對象時,該 std::shared_ptr 指針才會釋放其指向的對象;
- std::shared_ptr 的一般形式
- std::shared_ptr 初始化方式
- make_shared 函數
make_shared 函數能夠在動態內存(堆)中分配并初始化一個對象并返回指向該對象的?std::shared_ptr 指針,該方式無法自定義刪除器;
shared_ptr<int> p2 = std::make_shared<int>(100); // 該 shared_ptr 指向一個值為 100 的整型的內存,類似 int *pi = new int(100);shared_ptr<string> p3 = std::make_shared<string>(5, 'a'); // 5 個字符 a, 類似于 string mystr(5, 'a'); // 注意,make_shared 后圓括號里的參數的形式取決于 <> 中的類型名 // 此時這些參數必須和 string 里的某個構造函數匹配shared_ptr<int> p4 = make_shared<int>(); // p4 指向一個 int,int 里保存的值是 0 p4 = make_shared<int>(400); // p4釋放剛才的對象,重新指向新對象auto p5 = std::make_shared<string>(5, 'a'); // 用 auto 保存 make_shared 結果- std::shared_ptr 指針作為函數返回值
- std::shared_ptr 指針引用計數的增減
- std::shared_ptr 指針的常用操作
use_count 成員函數,返回指向某個對象的智能指針數量
shared_ptr<int> myp(new int(100)); int icount = myp.use_count(); // 1shared_ptr<int> myp2(myp); icount = myp.use_count(); // 2shared_ptr<int> myp3; myp3 = myp2; icount = myp.use_count(); // 3 icount = myp3.use_count(); // 3unique 成員函數,判斷該指針是否獨占某個指向的對象
shared_ptr<int> myp(new int(100)); if (myp.unique()) // 本條件成立 {// "myp" 獨占所指向的對象cout << "myp unique ok" << endl; }shared_ptr<int> myp2(myp); if (myp.unique()) // 本條件不再成立 {cout << "myp unique ok" << endl; }reset 成員函數
若 pi 是唯一指向該對象的指針,則釋放 pi 指向的對象并將 pi 置空 若 pi 不是唯一指向該對象的指針,則不釋放 pi 指向的對象,將該對象的引用計數減 1 并將 pi 置空 {shared_ptr<int> pi(new int(100));pi.reset(); // 釋放 pi 指向的對象,將 pi 置空if (pi == nullptr) // 條件成立{cout << "pi 被置空" << endl; } }{shared_ptr<int> pi(new int(100));auto pi2(pi); // pi2 引用計數現在為 2pi.reset(); // pi 被置空,pi2 引用計數變為 1 } reset(param) 若 pi 是唯一指向該對象的指針,則釋放 pi 指向的對象并將 pi 指向新內存 若 pi 不是唯一指向該對象的指針,則不釋放 pi 指向的對象,將該對象的引用計數減 1 并將 pi 指向新內存 {shared_ptr<int> pi(new int(100));// 釋放原內存 (內容為 100 的內存),指向新內存 (內容為 1 的內存)pi.reset(new int(1));cout << "斷點調試" << endl; }* 解引用
shared_ptr<int> pother(new int(12345)); char outbuf[1024]; sprintf_s(outbuf, sizeof(outbuf), "%d", *pother); // outbuf 中的內容為 "12345",pother 不發生任何變化,引用計數仍舊為 1get 成員函數
p.get(),返回 p 中保存的指針 注意,若智能指針釋放了所指向的對象,則返回的指針所指向的對象無效shared_ptr<int> myp(new int(100)); int* p = myp.get(); // myp.reset(); // 不可,結果無法預料 *p = 45; // delete p; // 不可,結果無法預料swap 成員函數,用于交換兩個智能指針所指向的對象,引用計數不會發生變化
shared_ptr<string> ps1(new string("I Love China1!")); shared_ptr<string> ps2(new string("I Love China2!")); std::swap(ps1, ps2); // 可以 ps1.swap(ps2); // 可以= nullptr
- 將所指向對象的引用計數減 1,若引用計數為 0,則釋放智能指針所指向的對象
- 將智能指針置空
智能指針名字作為判斷條件
shared_ptr<string> ps1(new string("I Love China1!")); //若 ps1 指向一個對象,則條件成立 if (ps1) { cout << "ps1" << endl; }指定刪除器
// 自定義刪除器,刪除整型指針,當 p 的引用計數為 0,則自動調用這個刪除器刪除對象,釋放內存 void myDeleter(int* p) {delete p; }shared_ptr<int> p(new int(12345), myDeleter); // 指定刪除器 shared_ptr<int> p2(p); // 現在兩個引用計數指向該對象 p2.reset(); // 現在一個引用計數指向該對象,p2 為 nullptr p.reset(); // 此時只有一個指針指向該對象,所以釋放指向的對象,調用自定義的刪除器 myDeleter,同時 p 為 nullptr{shared_ptr<int> p(new int(12345), [](int* p) {delete p;});p.reset(); // 會調用刪除器 (lambda 表達式) } 刪除數組 {shared_ptr<int[]> p(new int[10], [](int* p) {delete[] p; });p.reset(); }class A { public:A(){cout << "A()構造函數被調用" << endl;}~A(){cout << "~A()析構函數被調用" << endl;} };{// shared_ptr<A> pA(new A[10]);// 異常,因為系統釋放 pA 是使用 delete pA 而不是使用delete[] pA,所以必須自定義刪除器shared_ptr<A> pA(new A[10], [](A* p) { delete[] p; }); //一切正常 }{shared_ptr<A[]> pA(new A[10]); // <> 中加個 [],表示釋放數組// <> 中加個 [],而且加了 [] 后,引用也方便比如p[0]、p[1] ... p[9] 直接拿來用shared_ptr<int[]> p(new int[10]); } auto lambda1 = [](int* p) { delete p; };auto lambda2 = [](int* p) { delete p; };shared_ptr<int> p1(new int(100), lambda1); // 指定 lambda1 為刪除器 shared_ptr<int> p2(new int(200), lambda2); // 指定 lambda2 為刪除器 p2 = p1; // p2 會先調用 lambda2 把自己所指向對象釋放,然后指向 p1 所指對象,現在該對象引用計數為2 // 整個 main 函數執行完畢之前會調用 lambda1 釋放 p1, p2 共同指向的對象- std::shared_ptr 指針的使用場景
- std::shared_ptr 指針的使用陷阱
1. 慎用裸指針
void proc(shared_ptr<int> ptr) {return; }// int* p = new int(100); // 裸指針 // proc(p); // 語法錯 int *p 不能轉換成 shared_ptr<int> // proc(shared_ptr<int>(p)); // 參數是臨時 shared_ptr,用一個裸指針顯式構造的 // *p = 45; // 不可以預料到的結果;因為 p 指向的內存已經被釋放解決方案 shared_ptr<int> myp(new int(100)); proc(myp); *myp = 45; // myp 是 shared_ptr<...> 類型,* 代表解引用 // int* pi = new int; // shared_ptr<int> p1(pi); // shared_ptr<int> p2(pi); // p1 一個引用,p2 一個引用,會導致 p1, p2 兩個指針之間無關聯關系(每個的強引用計數都是 1), // 所以釋放時 pi 所指向的內存釋放 2 次,這顯然會出問題 shared_ptr<int> p1(new int); // 這種寫法至少大大降低了用 pi 來創建 p2 的可能性2. 慎用 get 返回的指針
// shared_ptr<int> myp(new int(100)); // int* p = myp.get(); // 返回 myp 中保存的指針 // delete p; // 不可以這樣,會導致異常 shared_ptr<int> myp(new int(100)); // 這個指針千萬不能隨便釋放,否則 myp 就沒有辦法正常管理該指針了 int* p = myp.get(); {// 這行代碼萬萬不可,現在 myp 和 myp2 引用計數都為 1,// 但是一旦跳出這個程序塊,這句代碼本身就會在程序執行結束時產生異常// shared_ptr<int> myp2(p);// 執行后 myp 和 myp3 引用計數都為2,跳出程序塊后,myp3 失效,myp 引用計數恢復為 1,myp 可以正常使用shared_ptr<int> myp2(myp); } // 離開上面這個 myp2 的有效范圍,導致 myp 指向的內存也被釋放了 // *myp = 100; // 該內存已經釋放,賦值會導致不可預料的后果3. 用?std::enable_shared_from_this 返回 this
class CT { public:shared_ptr<CT> getself(){return shared_ptr<CT>(this);} };shared_ptr<CT> pct1(new CT); shared_ptr<CT> pct2 = pct1; // 這沒問題,2 個強引用shared_ptr<CT> pct1(new CT); shared_ptr<CT> pct2 = pct1->getself(); // 問題出現使用同一個指針構造了兩個智能指針 pct1 和 pct2,且兩個智能指針之間沒有關系 // std::enable_shared_from_this // C++ 標準庫里提供的一個類模板// std::enable_shared_from_this 類型模板參數為繼承其子類的類名 // 該模板中存在一個 weak_ptr 用于觀測 this, // shared_from_this 方法內部實際調用了 weak_ptr 的 lock 方法, // 使得 shared_ptr 引用計數加 1 并返回該 shared_ptrclass CT :public std::enable_shared_from_this<CT> { public:shared_ptr<CT> getself(){// 這個是 enable_shared_from_this 類中方法,通過此方法返回智能指針return shared_from_this();} };shared_ptr<CT> pct1(new CT); shared_ptr<CT> pct2 = pct1->getself(); // 現在強引用計數為 24. 避免循環引用
【3】std::weak_ptr
std::weak_ptr 指向一個由?std::shared_ptr 管理的對象,但?std::weak_ptr 并不會控制所指向對象的生存期,即將?std::weak_ptr 綁定到?std::shared_ptr 上不會改變?std::shared_ptr 的引用計數,當?std::shared_ptr 需要釋放所指向的對象時不會考慮是否存在?std::weak_ptr 指向該對象;
- std::weak_ptr 指針常用操作
lock 成員函數
檢查?std::shared_ptr 所指向的對象是否存在,若存在則返回一個指向共享對象的?std::shared_ptr (原 std::shared_ptr 引用計數加 1),若不存在則返回一個空的?std::shared_ptr;
auto pi = make_shared<int>(100); // weak_ptr<int> piw(pi); // piw 弱共享 pi, pi 引用計數(強引用計數)不改變,弱引用計數字會從 0 變成 1;pi 和 piw 兩者指向相同位置weak_ptr<int> piw; piw = pi; // pi 這里是一個 shared_ptr,賦值給一個 weak_ptr,pi 和 piw 兩者指向相同位置weak_ptr<int> piw2; piw2 = piw; // 把 weak_ptr 賦給另外一個 weak_ptr,現在 pi 是 1 個強引用,2個弱引用auto pi2 = piw.lock(); // 強引用 (shared_ptr) 計數會加 1,現在 pi 是 2 個強引用,2 個弱引用 if (pi2 != nullptr) { cout << "所指對象存在" << endl; } auto pi = make_shared<int>(100); weak_ptr<int> piw(pi); // piw 弱共享 pi, pi 強引用計數不改變,弱引用計數字會從 0 變成 1 pi.reset(); // 因為 pi 是唯一指向該對象的指針,則釋放 pi 指向的對象,將 pi 置空auto pi2 = piw.lock(); // 因為所指向的對象被釋放了,所以 piw 弱引用也“過期”了 if (pi2 != nullptr) // 條件不再成立 {cout << "所指對象存在" << endl; }use_count 成員函數,獲取與該?std::weak_ptr 共享對象的其他?std::shared_ptr 的數量
expired 成員函數,判斷是否過期,若 use_count 為 0 則返回 true 否則返回 false;
reset 函數,將該弱引用指針設置為空,不影響指向該對象的強引用數量,將該對象的弱引用計數減 1;
auto pi = make_shared<int>(100); weak_ptr<int> piw(pi); piw.reset(); // pi 是 1 個強引用,無弱引用lock 函數,獲取所監視的 std::shared_ptr
auto p1 = make_shared<int>(42); weak_ptr<int> pw; pw = p1; // 可以用 shared_ptr 給 weak_ptr 值,現在 p1 是 1 個強引用,1 個弱引用 if (!pw.expired()) {auto p2 = pw.lock(); // 返回的 p2 是一個 shared_ptr,現在 p1 是 2 個強引用,1 個弱引用if (p2 != nullptr) // 條件成立{cout << "所指對象存在" << endl;} // 離開這個范圍,p1 的強引用計數恢復為 1,弱引用保持為 1 } else {cout << "pw已經過期" << endl; } //走到這里,p1 是 1 個強引用,1 個弱引用 weak_ptr<int> pw; {auto p1 = make_shared<int>(42);pw = p1; // 可以用 shared_ptr 給 weak_ptr 值 } // 離開這里時 p1 就失效了 //這里 pw 這個 weak_ptr 就會過期了 if (pw.expired()) // 條件成立 {cout << "pw已經過期" << endl; }【4】std::unique_ptr
std::unique_ptr 是一種獨占式的智能指針,即同一時刻只存在一個?std::unique_ptr 指針指向該對象(內存),當?std::unique_ptr 被銷毀時,其指向的對象也會被銷毀;
- std::unique_ptr 的一般形式
- std::unique_ptr 的初始化
- std::unique_ptr 常用操作
std::unique_ptr 不支持的操作
std::unique_ptr 是一個只能移動不能復制的類型;
unique_ptr<string> ps1(new string("I Love China!")); // unique_ptr<string> ps2(ps1); // 不可以,該智能指針不支持拷貝動作 // unique_ptr<string> ps3 = ps1; // 不可以,該智能指針不支持拷貝動作 // unique_ptr<string> ps4; // ps4 = ps1; // 不可以,該智能指針不支持賦值動作std::unique_ptr 的移動語義
unique_ptr<string> ps1(new string("I Love China!")); unique_ptr<string> ps3 = std::move(ps1); // 轉移后 ps1 為空了,ps3 指向原來 ps1 所指release 成員函數
放棄對指針的控制權,返回裸指針,將智能指針置空,該返回的裸指針可以手動 delete 釋放,也可以用于初始化另一個智能指針,或給另一個智能指針賦值;
// 將所有權從 ps1 轉移(移動)給 ps2 unique_ptr<string> ps1(new string("I Love China!")); unique_ptr<string> ps2(ps1.release()); if (ps1 == nullptr) // 條件成立 {cout << "ps1 被置空" << endl; } // ps2.release(); // 這會導致內存泄漏 string* tempp = ps2.release(); // 或者寫成 auto tempp = ps.release(); delete tempp;reset 成員函數
若 reset 沒有參數,釋放智能指針指向的對象,并將智能指針置空;若 reset 帶有參數,釋放智能指針原來所指向的內存,讓該智能指針指向新內存;
unique_ptr<string> prs(new string("I Love China!")); prs.reset(); // reset() 不帶參數時,釋放 prs 指向的對象,并將 prs 置空 if (prs == nullptr) // 條件成立 {cout << "prs被置空" << endl; } unique_ptr<string> prsdc(new string("I Love China 1!")); unique_ptr<string> prsdc2(new string("I Love China 2!")); // 當 prsdc2.reset(...) 中帶參數時,釋放 prsdc2 原來所指向的內存,讓 prsdc2 指向新內存 // reset 釋放原來 prsdc2 指向的對象內存,讓 prsdc2 指向 prsdc 所指向的內存,同時 prsdc 被置空 prsdc2.reset(prsdc.release()); // reset 參數可以是個裸指針,reset 釋放原來 prsdc2 指向的對象內存,讓 prsdc2 指向新 new 出來的 string prsdc2.reset(new string("I Love China!"));= nullptr
釋放智能指針所指向的對象,并將智能指針置空
unique_ptr<string> ps1(new string("I Love China!")); ps1 = nullptr; // 釋放 ps1 指向的對象,并將 ps1 置空指向一個數組
// 前面帶上空括號 [] 表示是數組,下面才可以用 [下標] 來引用數組元素 std::unique_ptr<int[]> ptrarray(new int[10]); // 數組提供索引運算符 [] // 能訪問的下標是 0-9,不要超過這個范圍,否則可能導致程序異常 ptrarray[0] = 12; ptrarray[1] = 24; ptrarray[9] = 124;get 成員函數
返回智能指針中保存的對象 (裸指針),注意不能 delete 獲取的指針;
unique_ptr<string> ps1(new string("I Love China!")); string* ps = ps1.get(); const char* p1 = ps->c_str(); *ps = "This is a test very good!"; // p1 和 p2 是不同的內存地址,這是 string 內部工作機制決定的 const char* p2 = ps->c_str();* 解引用
unique_ptr<string> ps1(new string("I Love China!")); const char* p1 = ps1->c_str(); *ps1 = "This is a test very good!"; // p1 和 p2 是不同的內存地址,這是 string 內部工作機制決定的 const char* p2 = ps1->c_str(); // 對于定義的內容是數組,是沒有 * 解引用運算符的 std::unique_ptr<int[]> ptrarray(new int[10]);swap 成員函數
用于交換兩個智能指針所指向的對象
unique_ptr<string> ps1(new string("I Love China1!")); unique_ptr<string> ps2(new string("I Love China2!")); std::swap(ps1, ps2); ps1.swap(ps2);智能指針名作為判斷條件
unique_ptr<string> ps1(new string("I Love China1!")); // 若 ps1 指向一個對象則為 true if (ps1) // 條件成立 {cout << "ps1指向了一個對象" << endl; }轉換成 std::shared_ptr 類型
auto myfunc() {// 這是一個右值 (短暫的臨時對象)return unique_ptr<string>(new string("I Love China!")); }// 可以成功,引用計數為 1 // 若 std::unique_ptr 為右值則可以賦值給 std::shared_ptr // 模板 std::shared_ptr 包含顯示構造函數,可用于將右值 std::unique_ptr 轉換為 std::shared_ptr shared_ptr<string> pss1 = myfunc();unique_ptr<std::string> ps(new std::string("I Love China!")); // 執行后 ps 為空,ps2 是 shared_ptr 且引用計數為 1 shared_ptr<string> ps2 = std::move(ps);返回 std::unique_ptr
若 std::unique_ptr 將被銷毀,則可以復制;
unique_ptr<string> tuniqp() {unique_ptr<string> pr(new string("I Love China!"));// 從函數返回一個局部 unique_ptr 對象是可以的// 返回局部對象 pr 會導致系統生成臨時 unique_ptr 對象// 并調用 unique_ptr 的移動構造函數return pr; }unique_ptr<string> ps; // 可以用 ps 接收 tuniqp 返回結果,則臨時對象直接構造在 ps 里 // 如果不接收,則臨時對象會釋放,同時釋放掉所指向的對象的內存 ps = tuniqp();指定刪除器
// 刪除器 void mydeleter(string* pdel) {delete pdel;pdel = nullptr; }{// 定義一個函數指針類型,類型名為 fptypedef void(*fp)(string*);unique_ptr<string, fp> ps1(new string("I Love China!"), mydeleter); }{// 用 using 定義一個函數指針類型,類型名為 fp2using fp2 = void(*)(string*);unique_ptr<string, fp2> ps2(new string("I Love China!"), mydeleter); }{// 注意這里多了個 *, 因為 decltype 是返回函數類型,加 * 表示函數指針類型// 現在 fp3 應該是 void *(string *)typedef decltype(mydeleter)* fp3;unique_ptr<string, fp3> ps3(new string("I Love China!"), mydeleter); }{std::unique_ptr<string, decltype(mydeleter)*> ps4(new string("I Love China!"), mydeleter); }{auto mydella = [](string* pdel) {delete pdel;pdel = nullptr;};std::unique_ptr<string, decltype(mydella)> ps5(new string("I Love China!"), mydella); }參考致謝
本博客為博主學習筆記,同時參考了網上眾博主的博文以及相關專業書籍,在此表示感謝,本文若存在不足之處,請批評指正。
【1】C++ 新經典
【2】C++智能指針之auto_ptr、unique_ptr、shared_ptr、weak_ptr的詳細介紹
【3】【C++】三種智能指針(auto_ptr,unique_ptr,shared_ptr)
【4】C++ 智能指針的使用
總結
以上是生活随笔為你收集整理的【C/C++基础进阶系列】C/C++ STL -- 智能指针的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux安装glassfish、利用g
- 下一篇: c++11并发与多线程