C++?smart?pointer全面深入講解
我們?yōu)槭裁葱枰猻mart pointer
眾所周知 新手寫的c++代碼是很恐怖 壓根就不能用 其中最大的原因就在于新手寫的代碼可能存在大量的內(nèi)存泄漏 那么為什么新手無法很好的去掌握內(nèi)存的東西呢 就是因為原生的c++并不像java那樣存在垃圾回收的機制 申請在堆區(qū)的資源都需要自己去回收 然而最痛苦的一件事情在于 指針的生命周期結(jié)束時 你會不小心就沒去回收他在堆區(qū)的資源 因為堆區(qū)資源的生命周期是很難把握的 有可能你析構(gòu)了 直接導(dǎo)致野指針訪問異常那么為了解決這個問題 c++就推出了智能指針 其中最重要的三種指針就是shared_ptr unique_ptr weak_ptr 接下來讓我們來講講如何將智能指針的生命周期和堆區(qū)資源的生命周期綁定起來吧
其實也非常簡單 本質(zhì)就是當這片堆區(qū)資源的引用計數(shù)變?yōu)?的時候就釋放這片內(nèi)存
smart pointer基本概念之引用計數(shù)
先來說說引用計數(shù) 這個東西是stl保證了肯定是線程安全的 所以即使你在多個線程內(nèi)同時去增加或者同時減少引用計數(shù)也并不會讓引用計數(shù)的值出現(xiàn)非你預(yù)期的結(jié)果
智能指針是和引用計數(shù)綁定在一起的 當你創(chuàng)建智能指針指向一片資源時 引用計數(shù)就加一 當智能指針析構(gòu)時 引用計數(shù)就減一 當引用計數(shù)變?yōu)?時 堆區(qū)資源被析構(gòu)
smart pointer之shared_ptr
讓我們來看看下一段代碼
int main() { std::shared_ptr<std::string> i(new std::string("its good")); std::shared_ptr<std::string> j(new std::string("its bad")); std::vector<std::shared_ptr<std::string>> smartPointer_vec; for(int k=0;k<5;k++) smartPointer_vec.emplace_back(i); for (int k = 0; k < 4; k++) smartPointer_vec.emplace_back(j); for (auto &i : smartPointer_vec) { std::cout<<i->c_str(); std::cout << i.use_count() << " "; std::cout << j.use_count() << std::endl; i = nullptr; } std::cout << i->c_str(); std::cout << i.use_count() <<" "; std::cout << j.use_count() << std::endl; }
聰明人看輸出 你就能完全明白 當引用計數(shù)為0的時候就會析構(gòu) 其他不多說了
重要講解:首先使用share_ptr去指向new出來的數(shù)據(jù)是性能低效的 最本質(zhì)的原因在于 他會進行兩次內(nèi)存分配 第一次是對象堆區(qū)資源的申請 然后才是引用計數(shù)堆區(qū)資源的申請 而使用make_shared可以只進行一次內(nèi)存分配 所以他更快 并且更安全 并且c++標準委員會也推薦你這么做 關(guān)于make_shared等下講解
自定義deleter(也就是自定義刪除器)
先說我們?yōu)槭裁葱枰远x刪除器 因為在某些情況下 我們希望當智能指針指向的堆區(qū)資源釋放的時候進行一些自定義操作也就是說你可以玩一些很花的操作 但是也是那句話 stl并不會執(zhí)行任何安全檢查 崩了需要自己負責(zé)并且總所周知 new []這種形式的堆區(qū)資源需要我們使用delete[]來釋放 這就是最大的問題 shared_ptr默認是使用delete的 也就是說 當你使用shared_ptr去指向new []時如果不自定義刪除器 必然會造成內(nèi)存泄漏 如下圖所示的一段代碼就是經(jīng)典的內(nèi)存泄漏
正確的寫法如下
即自定義一個刪除器 當然你也可以玩一些移動操作 也就是花哨的操作 當然花哨操作就很多了 我只演示其中一種如下圖所示
運行結(jié)果截圖如下:
Tips:當你非常清楚你在干什么的時候再玩 功力不夠 不要亂玩
shared_ptr之make_shared
上文我們說過 使用智能指針指向new出來的資源有一個問題就是他會進行兩次內(nèi)存分配 而標準委員會推薦創(chuàng)建shared_ptr的方式是使用make_shared 讓我們來看看make_shared是如何進行堆區(qū)資源申請的 一個最簡單的例子如下
int main() { std::shared_ptr<int>p1(new int(5)); //下面這種方式比上面這種方式性能更快 并且更加安全 std::shared_ptr<int>p2 = make_shared<int>(5); }
當你使用make_shared的時候 又想去使用智能指針指向一個數(shù)組的時候 一個推薦的做法如下
int main() { std::shared_ptr<std::vector<int>>p1(new std::vector<int>()); //下面這種方式比上面這種方式性能更快 并且更加安全 std::shared_ptr<std::vector<int>>p2 = make_shared<std::vector<int>>(); }
智能指針存在的問題之循環(huán)引用
那么現(xiàn)在我們來看看shared_ptr存在的一些問題 其中比較著名的一個問題就是循環(huán)引用 什么叫循環(huán)引用呢 本人的觀點是當你的智能指針指向的A堆區(qū)資源里又有智能指針去指向B堆區(qū)資源 而B堆區(qū)資源又存在一個智能指針來指向A堆區(qū)資源 而你能拿到的指針對半是全局或者是棧區(qū)的智能指針 你無法干預(yù)到堆區(qū)的智能指針的釋放 下面來看一個最簡單的例子造成的循環(huán)引用 代碼如下圖所示
class SmartPointerTest { public: std::shared_ptr<SmartPointerTest> LoopRef{}; int p[1000]{}; }; int main() { std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest()); std::shared_ptr<SmartPointerTest>p2(new SmartPointerTest()); p1->LoopRef = p2; p2->LoopRef = p1; }
可以明顯看到 我們創(chuàng)建了兩個智能指針p1和p2 而p1指向的堆區(qū)資源里又有智能指針指向p2的堆區(qū)資源 同理p2 而當main函數(shù)結(jié)束的時候 p1 p2指針被釋放 但是 這個時候 因為兩片堆區(qū)資源的引用計數(shù)都沒被置為0 所以不會釋放 那么這片堆區(qū)內(nèi)存也就永遠的泄漏了 這是所有循環(huán)引用的原型 無論任何再復(fù)雜的循環(huán)引用都是建立在這個最基本的循環(huán)引用之上的
解決循環(huán)引用之weak_ptr
我們現(xiàn)在希望有一個方法來解決循環(huán)引用的問題 并且我們也想去隨時拿到資源 那么我們該如何做呢 標準委員會也考慮到了這個問題 于是他提供了weak_ptr 當他指向一片堆區(qū)資源的時候 并不會讓這片堆區(qū)資源的引用計數(shù)加一 而是作為這片資源的觀察者 當需要這片資源的時候 隨時使用lock()函數(shù)來獲得一個shared_ptr來進行使用 下面讓我們來看看如何使用weak_ptr 基于上面的例子
class SmartPointerTest { public: std::weak_ptr<SmartPointerTest> LoopRef{}; int p[1000]{}; }; int main() { std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest()); std::shared_ptr<SmartPointerTest>p2(new SmartPointerTest()); p1->LoopRef = p2; p2->LoopRef = p1; //當你想使用資源的時候 用下面的操作進行 std::cout << p1->LoopRef.lock()->p << std::endl; }
輸出結(jié)果如下:
Tips:當然weak_ptr的作用遠遠不止如此 他存在的意義僅僅是你想共享資源但是你并不想增加引用計數(shù) 解決循環(huán)引用只是順便解決的優(yōu)秀的程序員總是能知道在什么情況下使用何種指針來達到性能最優(yōu) lock()函數(shù) 顧名思義是要去給引用計數(shù)上鎖的 頻繁上鎖帶來的性能問題不用多說了吧
如果weak_ptr指向的資源已經(jīng)被析構(gòu) 那么他會拋出bad_weak_ptr的異常 請注意捕獲異常
智能指針問題
無法創(chuàng)建指向自己的智能指針(本質(zhì)當創(chuàng)建自己的智能指針時會創(chuàng)建兩個所屬組)
什么叫無法創(chuàng)建指向自己的智能指針呢 看如下這段代碼
class SmartPointerTest { public: std::weak_ptr<SmartPointerTest> LoopRef{}; int p[1000]{}; std::vector<std::shared_ptr<SmartPointerTest>> spt_vec; void MemberFuncTest() { spt_vec.push_back(std::shared_ptr<SmartPointerTest>(this)); } int operator[](int i) { return p[i]; } }; int main() { std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest()); p1->MemberFuncTest(); std::cout<<p1.use_count()<<std::endl; system("pause"); }
我們預(yù)期的結(jié)果是把指向自己的智能指針傳入 并且引用計數(shù)為2 但是運行結(jié)果如下:
并且程序會崩潰 為什么呢 因為你重復(fù)釋放了 這就是我說的 你會創(chuàng)建兩個組 而不是單純的增加引用計數(shù) 其本質(zhì)還是濫用普通指針和智能指針引起的麻煩
解決方法如下
代碼如下 我們可以繼承于std::enable_shared_from_this來解決
class SmartPointerTest :std::enable_shared_from_this<SmartPointerTest> { public: std::weak_ptr<SmartPointerTest> LoopRef{}; int p[1000]{}; std::vector<std::shared_ptr<SmartPointerTest>> spt_vec; void MemberFuncTest() { spt_vec.push_back(std::shared_ptr<SmartPointerTest>(shared_from_this())); } int operator[](int i) { return p[i]; } }; int main() { std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest()); p1->MemberFuncTest(); std::cout<<p1.use_count()<<std::endl; system("pause"); }
當你這樣繼承自enable_shared_from_this的時候你就可以將自身的智能指針傳入而不是創(chuàng)建一個新的組避免了重復(fù)釋放非常的方便
關(guān)于unique_ptr我們將會在下一篇文章進行詳細講解其實也很簡單就是他堆區(qū)資源的引用計數(shù)永遠只可能是一也就是說他的資源只可能被一個指針指向附帶而來的有一些小細節(jié)和普通的shared_ptr不同我們也就留在下一章再說了
到此這篇關(guān)于C++ smart pointer全面深入講解的文章就介紹到這了,更多相關(guān)C++ smart pointer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言中的時間函數(shù)clock()和time()你都了解嗎
這篇文章主要為大家詳細介紹了C語言中的時間函數(shù)clock()和time(),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02Cocos2d-x UI開發(fā)之CCControlPotentiometer控件類使用實例
這篇文章主要介紹了Cocos2d-x UI開發(fā)之CCControlPotentiometer控件類使用實例,本文代碼中包含注釋來講解CCControlPotentiometer控件類的使用,需要的朋友可以參考下2014-09-09C語言中分支和循環(huán)的6種實現(xiàn)形式總結(jié)
C語言時一門結(jié)構(gòu)化的程序設(shè)計語言,這篇文章主要介紹了C語言中的分支和循環(huán)的6種實現(xiàn)形式,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2023-04-04