<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
眾所周知 新手寫的c++程式碼是很恐怖 壓根就不能用 其中最大的原因就在於新手寫的程式碼可能存在大量的記憶體漏失 那麼為什麼新手無法很好的去掌握記憶體的東西呢 就是因為原生的c++並不像java那樣存在垃圾回收的機制 申請在堆區的資源都需要自己去回收 然而最痛苦的一件事情在於 指標的生命週期結束時 你會不小心就沒去回收他在堆區的資源 因為堆區資源的生命週期是很難把握的 有可能你解構了 直接導致野指標存取異常那麼為了解決這個問題 c++就推出了智慧指標 其中最重要的三種指標就是shared_ptr unique_ptr weak_ptr 接下來讓我們來講講如何將智慧指標的生命週期和堆區資源的生命週期繫結起來吧
其實也非常簡單 本質就是當這片堆區資源的參照計數變為0的時候就釋放這片記憶體
先來說說參照計數 這個東西是stl保證了肯定是執行緒安全的 所以即使你在多個執行緒內同時去增加或者同時減少參照計數也並不會讓參照計數的值出現非你預期的結果
智慧指標是和參照計數繫結在一起的 當你建立智慧指標指向一片資源時 參照計數就加一 當智慧指標解構時 參照計數就減一 當參照計數變為0時 堆區資源被解構
讓我們來看看下一段程式碼
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; }
聰明人看輸出 你就能完全明白 當參照計數為0的時候就會解構 其他不多說了
重要講解:首先使用share_ptr去指向new出來的資料是效能低效的 最本質的原因在於 他會進行兩次記憶體分配 第一次是物件堆區資源的申請 然後才是參照計數堆區資源的申請 而使用make_shared可以只進行一次記憶體分配 所以他更快 並且更安全 並且c++標準委員會也推薦你這麼做 關於make_shared等下講解
先說我們為什麼需要自定義刪除器 因為在某些情況下 我們希望當智慧指標指向的堆區資源釋放的時候進行一些自定義操作也就是說你可以玩一些很花的操作 但是也是那句話 stl並不會執行任何安全檢查 崩了需要自己負責並且總所周知 new []這種形式的堆區資源需要我們使用delete[]來釋放 這就是最大的問題 shared_ptr預設是使用delete的 也就是說 當你使用shared_ptr去指向new []時如果不自定義刪除器 必然會造成記憶體漏失 如下圖所示的一段程式碼就是經典的記憶體漏失
正確的寫法如下
即自定義一個刪除器 當然你也可以玩一些移動操作 也就是花哨的操作 當然花哨操作就很多了 我只演示其中一種如下圖所示
執行結果截圖如下:
Tips:當你非常清楚你在幹什麼的時候再玩 功力不夠 不要亂玩
上文我們說過 使用智慧指標指向new出來的資源有一個問題就是他會進行兩次記憶體分配 而標準委員會推薦建立shared_ptr的方式是使用make_shared 讓我們來看看make_shared是如何進行堆區資源申請的 一個最簡單的例子如下
int main() { std::shared_ptr<int>p1(new int(5)); //下面這種方式比上面這種方式效能更快 並且更加安全 std::shared_ptr<int>p2 = make_shared<int>(5); }
當你使用make_shared的時候 又想去使用智慧指標指向一個陣列的時候 一個推薦的做法如下
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>>(); }
那麼現在我們來看看shared_ptr存在的一些問題 其中比較著名的一個問題就是迴圈參照 什麼叫回圈參照呢 本人的觀點是當你的智慧指標指向的A堆區資源裡又有智慧指標去指向B堆區資源 而B堆區資源又存在一個智慧指標來指向A堆區資源 而你能拿到的指標對半是全域性或者是棧區的智慧指標 你無法干預到堆區的智慧指標的釋放 下面來看一個最簡單的例子造成的迴圈參照 程式碼如下圖所示
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; }
可以明顯看到 我們建立了兩個智慧指標p1和p2 而p1指向的堆區資源裡又有智慧指標指向p2的堆區資源 同理p2 而當main函數結束的時候 p1 p2指標被釋放 但是 這個時候 因為兩片堆區資源的參照計數都沒被置為0 所以不會釋放 那麼這片堆區記憶體也就永遠的洩漏了 這是所有迴圈參照的原型 無論任何再複雜的迴圈參照都是建立在這個最基本的迴圈參照之上的
我們現在希望有一個方法來解決迴圈參照的問題 並且我們也想去隨時拿到資源 那麼我們該如何做呢 標準委員會也考慮到了這個問題 於是他提供了weak_ptr 當他指向一片堆區資源的時候 並不會讓這片堆區資源的參照計數加一 而是作為這片資源的觀察者 當需要這片資源的時候 隨時使用lock()函數來獲得一個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; }
輸出結果如下:
Tips:當然weak_ptr的作用遠遠不止如此 他存在的意義僅僅是你想共用資源但是你並不想增加參照計數 解決迴圈參照只是順便解決的優秀的程式設計師總是能知道在什麼情況下使用何種指標來達到效能最優 lock()函數 顧名思義是要去給參照計數上鎖的 頻繁上鎖帶來的效能問題不用多說了吧
如果weak_ptr指向的資源已經被解構 那麼他會丟擲bad_weak_ptr的異常 請注意捕獲異常
無法建立指向自己的智慧指標(本質當建立自己的智慧指標時會建立兩個所屬組)
什麼叫無法建立指向自己的智慧指標呢 看如下這段程式碼
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"); }
我們預期的結果是把指向自己的智慧指標傳入 並且參照計數為2 但是執行結果如下:
並且程式會崩潰 為什麼呢 因為你重複釋放了 這就是我說的 你會建立兩個組 而不是單純的增加參照計數 其本質還是濫用普通指標和智慧指標引起的麻煩
程式碼如下 我們可以繼承於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的時候你就可以將自身的智慧指標傳入而不是建立一個新的組避免了重複釋放非常的方便
關於unique_ptr我們將會在下一篇文章進行詳細講解其實也很簡單就是他堆區資源的參照計數永遠只可能是一也就是說他的資源只可能被一個指標指向附帶而來的有一些小細節和普通的shared_ptr不同我們也就留在下一章再說了
到此這篇關於C++ smart pointer全面深入講解的文章就介紹到這了,更多相關C++ smart pointer內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45