C++ 智能指針使用不當(dāng)導(dǎo)致內(nèi)存泄漏問(wèn)題解析
shared_ptr相互嵌套導(dǎo)致循環(huán)引用
代碼示例
#include <iostream> #include <memory> using namespace std; class B; class A { public: std::shared_ptr<B> b_ptr; ~A() { std::cout << "A destroyed\n"; } }; class B { public: std::shared_ptr<A> a_ptr; ~B() { std::cout << "B destroyed\n"; } }; int main() { // 創(chuàng)建 shared_ptr 對(duì)象 auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); // 相互引用 a->b_ptr = b; b->a_ptr = a; cout<<"use_count of a:"<<a.use_count()<<endl; cout<<"use_count of b:"<<b.use_count()<<endl; return 0; }
解釋說(shuō)明
- 創(chuàng)建了兩個(gè)
std::shared_ptr
對(duì)象a
和b
。 a
持有b
的shared_ptr
,b
持有a
的shared_ptr
。- 當(dāng)
main
函數(shù)結(jié)束時(shí),a
和b
的引用計(jì)數(shù)不會(huì)減少到零,因此它們的析構(gòu)函數(shù)不會(huì)被調(diào)用。 - 導(dǎo)致內(nèi)存泄漏,因?yàn)閷?duì)象
A
和B
的內(nèi)存不會(huì)被釋放。
解決方法
為了避免這種循環(huán)引用的問(wèn)題,可以使用 std::weak_ptr
。std::weak_ptr
是一種弱智能指針,它不會(huì)增加對(duì)象的引用計(jì)數(shù)。它可以用來(lái)打破循環(huán)引用,從而防止內(nèi)存泄漏。
#include <iostream> #include <memory> using namespace std; class B; // 先聲明類 B,使得 A 和 B 可以互相引用。 class A { public: std::shared_ptr<B> b_ptr; // A 擁有 B 的強(qiáng)引用 ~A() { std::cout << "A destroyed\n"; } }; class B { public: std::weak_ptr<A> a_ptr; // B 擁有 A 的弱引用 ~B() { std::cout << "B destroyed\n"; } void safeAccess() { // 嘗試鎖定 a_ptr 獲取 shared_ptr if (auto a_shared = a_ptr.lock()) { // 安全訪問(wèn) a_shared 對(duì)象 std::cout << "Accessing A from B\n"; } else { std::cout << "A is already destroyed, cannot access A from B\n"; } } }; int main() { // 創(chuàng)建 shared_ptr 對(duì)象 auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); // 互相引用 a->b_ptr = b; b->a_ptr = a; // 安全訪問(wèn) b->safeAccess(); cout<<"use_count of a:"<<a.use_count()<<endl; cout<<"use_count of b:"<<b.use_count()<<endl; return 0; // 在這里,a 和 b 的引用計(jì)數(shù)將會(huì)正確地減少到零,并且它們將會(huì)被銷毀。 }
shared_ptr的層次使用沒(méi)有導(dǎo)致循環(huán)引用
shared_ptr<vector<shared_ptr<pair<string, shared_ptr<string>>>>> jsFiles;
這個(gè)聲明表示 jsFiles
是一個(gè) std::shared_ptr
,它指向一個(gè) std::vector
,向量中的每個(gè)元素是一個(gè) std::shared_ptr
,指向一個(gè) std::pair
對(duì)象,而這個(gè) std::pair
對(duì)象中包含一個(gè) std::string
和一個(gè) std::shared_ptr<std::string>
。它們之間只是層次結(jié)構(gòu),沒(méi)有跨層次的相互引用 。也就是說(shuō)沒(méi)有內(nèi)存泄漏的問(wèn)題。證明如下:
#include <iostream> #include <vector> #include <memory> #include <string> using namespace std; // 自定義 String 類,模擬 std::string class MyString { public: std::string data; MyString(const std::string& str) : data(str) { std::cout << "MyString created: " << data << std::endl; } ~MyString() { std::cout << "MyString destroyed: " << data << std::endl; } // 添加輸出操作符重載 friend std::ostream& operator<<(std::ostream& os, const MyString& myStr) { os << myStr.data; return os; } }; // 自定義 Pair 類,模擬 std::pair template<typename K, typename V> class MyPair { public: K first; V second; MyPair(const K& key, const V& value) : first(key), second(value) { std::cout << "MyPair created: {" << first << ", " << *second << "}" << std::endl; } ~MyPair() { std::cout << "MyPair destroyed: {" << first << ", " << *second << "}" << std::endl; } }; int main() { // 創(chuàng)建 jsFiles,它是一個(gè) shared_ptr,指向 vector auto jsFiles = std::make_shared<std::vector<std::shared_ptr<MyPair<std::string, std::shared_ptr<MyString>>>>>(); // 添加元素 auto innerPair1 = std::make_shared<MyPair<std::string, std::shared_ptr<MyString>>>("file1", std::make_shared<MyString>("content of file1")); auto innerPair2 = std::make_shared<MyPair<std::string, std::shared_ptr<MyString>>>("file2", std::make_shared<MyString>("content of file2")); jsFiles->push_back(innerPair1); jsFiles->push_back(innerPair2); // 訪問(wèn)元素 for (const auto& pairPtr : *jsFiles) { std::cout << "Filename: " << pairPtr->first << ", Content: " << *pairPtr->second << std::endl; } // 離開作用域時(shí),智能指針會(huì)自動(dòng)銷毀它們管理的對(duì)象 return 0; }
同時(shí)也證明了一個(gè)結(jié)論,構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用順序是相反的。
回調(diào)函數(shù)中的循環(huán)引用問(wèn)題
值捕獲
#include <iostream> #include <memory> #include <functional> class MyClass { public: MyClass() { std::cout << "MyClass created" << std::endl; } ~MyClass() { std::cout << "MyClass destroyed" << std::endl; } void setCallback(std::function<void()> cb) { callback_ = cb; } void executeCallback() { if (callback_) { callback_(); } } private: std::function<void()> callback_; }; void createNoLeak() { auto myObject = std::make_shared<MyClass>(); myObject->setCallback([=]() { std::cout << "Callback executed, myObject use count: " << myObject.use_count() << std::endl; }); myObject->executeCallback(); } int main() { createNoLeak(); std::cout << "End of program" << std::endl; return 0; }
可以看出myObject最后沒(méi)有調(diào)用析構(gòu)函數(shù),是shared_ptr循環(huán)引用了。
引用捕獲
如果換為引用捕獲,則不會(huì)造成 shared_ptr循環(huán)引用。雖然這種方式不會(huì)增加引用計(jì)數(shù),但需要特別注意捕獲對(duì)象的生命周期,防止在 lambda 被調(diào)用時(shí),對(duì)象已經(jīng)被銷毀,從而導(dǎo)致未定義行為。
如何解決
#include <iostream> #include <memory> #include <functional> class MyClass : public std::enable_shared_from_this<MyClass> { public: MyClass() { std::cout << "MyClass created" << std::endl; } ~MyClass() { std::cout << "MyClass destroyed" << std::endl; } void setCallback(std::function<void()> cb) { callback_ = cb; } void executeCallback() { if (callback_) { callback_(); } } private: std::function<void()> callback_; }; void createNoLeak() { auto myObject = std::make_shared<MyClass>(); std::weak_ptr<MyClass> weakPtr = myObject; myObject->setCallback([weakPtr]() { if (auto sharedPtr = weakPtr.lock()) { std::cout << "Callback executed, object is valid" << std::endl; } else { std::cout << "Object already destroyed" << std::endl; } }); myObject->executeCallback(); // 這里 myObject 是按 weak_ptr 捕獲,當(dāng) createNoLeak() 結(jié)束時(shí),myObject 的生命周期也就結(jié)束了,并且引用計(jì)數(shù)=0 } int main() { createNoLeak(); std::cout << "End of program" << std::endl; return 0; }
weakPtr.lock()
的使用:持有std::weak_ptr
,并且需要檢查或者使用其管理的對(duì)象。如果對(duì)象仍然存在(即它的shared_ptr
引用計(jì)數(shù)大于零),我們希望獲取一個(gè)shared_ptr
來(lái)安全地使用該對(duì)象。否則,weak_ptr.lock()
返回一個(gè)空的shared_ptr
。std::enable_shared_from_this
是一個(gè)非常有用的標(biāo)準(zhǔn)庫(kù)模板類,用于解決一個(gè)特定的問(wèn)題: 當(dāng)一個(gè)類的成員函數(shù)需要?jiǎng)?chuàng)建一個(gè)指向自己(this
)的std::shared_ptr
時(shí),這類問(wèn)題如何安全地實(shí)現(xiàn)。std::enable_shared_from_this
背景問(wèn)題
在使用 std::shared_ptr
管理對(duì)象時(shí),有時(shí)會(huì)遇到需要在類的成員函數(shù)中獲取該對(duì)象的 shared_ptr
的情況。例如,在一個(gè)類的成員函數(shù)中,如果想要得到一個(gè)指向該對(duì)象的 shared_ptr
,不能簡(jiǎn)單地使用 std::shared_ptr<MyClass>(this)
,因?yàn)檫@會(huì)創(chuàng)建一個(gè)新的 shared_ptr
,而不是增加現(xiàn)有的 shared_ptr
的引用計(jì)數(shù)。這可能導(dǎo)致對(duì)象被提前銷毀或者多次銷毀。
std::enable_shared_from_this 的作用
通過(guò)繼承 std::enable_shared_from_this
,類就能夠安全地使用 shared_from_this
方法,從而獲取一個(gè) shared_ptr
,該 shared_ptr
與其他 shared_ptr
共享所有權(quán),而不會(huì)重復(fù)增加引用計(jì)數(shù)。
使用示例
#include <iostream> #include <memory> // 定義 MyClass 繼承 std::enable_shared_from_this<MyClass> class MyClass : public std::enable_shared_from_this<MyClass> { public: MyClass() { std::cout << "MyClass created" << std::endl; } ~MyClass() { std::cout << "MyClass destroyed" << std::endl; } // 一個(gè)成員函數(shù),它需要返回一個(gè)指向自身的 shared_ptr std::shared_ptr<MyClass> getSharedPtr() { // 使用 shared_from_this 返回一個(gè) shared_ptr return shared_from_this(); } void doSomething() { auto ptr = shared_from_this(); // 獲取 shared_ptr std::cout << "Doing something with MyClass instance, ref count: " << ptr.use_count() << std::endl; } }; void exampleFunction() { // 創(chuàng)建 MyClass 對(duì)象的 shared_ptr auto myObject = std::make_shared<MyClass>(); // 調(diào)用成員函數(shù)獲取 shared_ptr auto mySharedPtr = myObject->getSharedPtr(); std::cout << "Reference count after getSharedPtr: " << mySharedPtr.use_count() << std::endl; myObject->doSomething(); } int main() { exampleFunction(); return 0; }
注意
1.創(chuàng)建對(duì)象:
只有通過(guò) std::shared_ptr
創(chuàng)建或管理的對(duì)象,才能安全地使用 shared_from_this
。
2. 保護(hù)避免使用 new
操作符:
直接使用 new
操作符創(chuàng)建的對(duì)象不能正確使用 shared_from_this
,這樣做可能會(huì)導(dǎo)致未定義行為(例如崩潰)。
為什么 std::enable_shared_from_this 是必要的?
std::enable_shared_from_this
內(nèi)部維護(hù)了一個(gè)弱引用(std::weak_ptr
)指向當(dāng)前對(duì)象。這個(gè)弱引用確保不會(huì)增加引用計(jì)數(shù),同時(shí)允許 shared_from_this
方法安全地獲取 std::shared_ptr
,從而真正共享管理的對(duì)象,避免不安全的重復(fù)引用計(jì)數(shù)增加。
通過(guò)這樣做,C++ STL 提供了一種方便而安全的方式來(lái)管理對(duì)象的生命周期,特別是在需要從對(duì)象內(nèi)部生成 shared_ptr
的情境下。
總結(jié)
通過(guò)繼承 std::enable_shared_from_this
,MyClass
能夠安全地在其成員函數(shù)中創(chuàng)建返回指向自身的 std::shared_ptr
,避免不必要的重復(fù)引用計(jì)數(shù),從而有效地管理和共享對(duì)象生命周期。這樣既提升了代碼的安全性,也使得對(duì)象生命周期管理變得更加簡(jiǎn)潔和直觀。
到此這篇關(guān)于C++ 智能指針使用不當(dāng)導(dǎo)致內(nèi)存泄漏問(wèn)題的文章就介紹到這了,更多相關(guān)C++ 智能指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)高精度加法的示例代碼
高精度的本質(zhì)是將數(shù)字以字符串的形式讀入,然后將每一位分別存放入int數(shù)組中,通過(guò)模擬每一位的運(yùn)算過(guò)程,來(lái)實(shí)現(xiàn)最終的運(yùn)算效果,下面我們就來(lái)看看如何通過(guò)C語(yǔ)言實(shí)現(xiàn)高精度加法吧2023-11-11C++計(jì)算ICMP頭的校驗(yàn)和實(shí)例
這篇文章主要介紹了C++計(jì)算ICMP頭的校驗(yàn)和的方法,代碼簡(jiǎn)單實(shí)用,對(duì)于校驗(yàn)ICMP報(bào)文來(lái)說(shuō)有不錯(cuò)的實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10重學(xué)c/c++之?dāng)?shù)據(jù)存儲(chǔ)詳解(整數(shù)、浮點(diǎn)數(shù))
C語(yǔ)言給定了一些基本的數(shù)據(jù)類型,下面這篇文章主要給大家介紹了關(guān)于重學(xué)c/c++之?dāng)?shù)據(jù)存儲(chǔ)(整數(shù)、浮點(diǎn)數(shù))的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11C++ Opencv imfill孔洞填充函數(shù)的實(shí)現(xiàn)思路與代碼
在Matlab下,使用imfill可以很容易的完成孔洞填充操作,下面這篇文章主要給大家介紹了關(guān)于C++ Opencv imfill孔洞填充函數(shù)的實(shí)現(xiàn)思路與代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09