您好,登錄后才能下訂單哦!
本篇內容介紹了“C++ smart pointer是什么及怎么用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
眾所周知 新手寫的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的時候你就可以將自身的智能指針傳入而不是創建一個新的組避免了重復釋放非常的方便
“C++ smart pointer是什么及怎么用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。