C++ 智能指針使用不當(dāng)導(dǎo)致內(nèi)存泄漏問題解析
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 對象
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;
}
解釋說明
- 創(chuàng)建了兩個
std::shared_ptr對象a和b。 a持有b的shared_ptr,b持有a的shared_ptr。- 當(dāng)
main函數(shù)結(jié)束時,a和b的引用計數(shù)不會減少到零,因此它們的析構(gòu)函數(shù)不會被調(diào)用。 - 導(dǎo)致內(nèi)存泄漏,因為對象
A和B的內(nèi)存不會被釋放。
解決方法
為了避免這種循環(huán)引用的問題,可以使用 std::weak_ptr。std::weak_ptr 是一種弱智能指針,它不會增加對象的引用計數(shù)。它可以用來打破循環(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 的強引用
~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()) {
// 安全訪問 a_shared 對象
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 對象
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
// 互相引用
a->b_ptr = b;
b->a_ptr = a;
// 安全訪問
b->safeAccess();
cout<<"use_count of a:"<<a.use_count()<<endl;
cout<<"use_count of b:"<<b.use_count()<<endl;
return 0; // 在這里,a 和 b 的引用計數(shù)將會正確地減少到零,并且它們將會被銷毀。
}
shared_ptr的層次使用沒有導(dǎo)致循環(huán)引用
shared_ptr<vector<shared_ptr<pair<string, shared_ptr<string>>>>> jsFiles;
這個聲明表示 jsFiles 是一個 std::shared_ptr,它指向一個 std::vector,向量中的每個元素是一個 std::shared_ptr,指向一個 std::pair 對象,而這個 std::pair 對象中包含一個 std::string 和一個 std::shared_ptr<std::string>。它們之間只是層次結(jié)構(gòu),沒有跨層次的相互引用 。也就是說沒有內(nèi)存泄漏的問題。證明如下:
#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,它是一個 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);
// 訪問元素
for (const auto& pairPtr : *jsFiles) {
std::cout << "Filename: " << pairPtr->first << ", Content: " << *pairPtr->second << std::endl;
}
// 離開作用域時,智能指針會自動銷毀它們管理的對象
return 0;
}
同時也證明了一個結(jié)論,構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用順序是相反的。
回調(diào)函數(shù)中的循環(huá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最后沒有調(diào)用析構(gòu)函數(shù),是shared_ptr循環(huán)引用了。
引用捕獲
如果換為引用捕獲,則不會造成 shared_ptr循環(huán)引用。雖然這種方式不會增加引用計數(shù),但需要特別注意捕獲對象的生命周期,防止在 lambda 被調(diào)用時,對象已經(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é)束時,myObject 的生命周期也就結(jié)束了,并且引用計數(shù)=0
}
int main() {
createNoLeak();
std::cout << "End of program" << std::endl;
return 0;
}
weakPtr.lock()的使用:持有std::weak_ptr,并且需要檢查或者使用其管理的對象。如果對象仍然存在(即它的shared_ptr引用計數(shù)大于零),我們希望獲取一個shared_ptr來安全地使用該對象。否則,weak_ptr.lock()返回一個空的shared_ptr。std::enable_shared_from_this是一個非常有用的標(biāo)準(zhǔn)庫模板類,用于解決一個特定的問題: 當(dāng)一個類的成員函數(shù)需要創(chuàng)建一個指向自己(this)的std::shared_ptr時,這類問題如何安全地實現(xiàn)。std::enable_shared_from_this
背景問題
在使用 std::shared_ptr 管理對象時,有時會遇到需要在類的成員函數(shù)中獲取該對象的 shared_ptr 的情況。例如,在一個類的成員函數(shù)中,如果想要得到一個指向該對象的 shared_ptr,不能簡單地使用 std::shared_ptr<MyClass>(this),因為這會創(chuàng)建一個新的 shared_ptr,而不是增加現(xiàn)有的 shared_ptr 的引用計數(shù)。這可能導(dǎo)致對象被提前銷毀或者多次銷毀。
std::enable_shared_from_this 的作用
通過繼承 std::enable_shared_from_this,類就能夠安全地使用 shared_from_this 方法,從而獲取一個 shared_ptr,該 shared_ptr 與其他 shared_ptr 共享所有權(quán),而不會重復(fù)增加引用計數(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; }
// 一個成員函數(shù),它需要返回一個指向自身的 shared_ptr
std::shared_ptr<MyClass> getSharedPtr() {
// 使用 shared_from_this 返回一個 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 對象的 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)建對象:
只有通過 std::shared_ptr 創(chuàng)建或管理的對象,才能安全地使用 shared_from_this。
2. 保護(hù)避免使用 new 操作符:
直接使用 new 操作符創(chuàng)建的對象不能正確使用 shared_from_this,這樣做可能會導(dǎo)致未定義行為(例如崩潰)。
為什么 std::enable_shared_from_this 是必要的?
std::enable_shared_from_this 內(nèi)部維護(hù)了一個弱引用(std::weak_ptr)指向當(dāng)前對象。這個弱引用確保不會增加引用計數(shù),同時允許 shared_from_this 方法安全地獲取 std::shared_ptr,從而真正共享管理的對象,避免不安全的重復(fù)引用計數(shù)增加。
通過這樣做,C++ STL 提供了一種方便而安全的方式來管理對象的生命周期,特別是在需要從對象內(nèi)部生成 shared_ptr的情境下。
總結(jié)
通過繼承 std::enable_shared_from_this,MyClass 能夠安全地在其成員函數(shù)中創(chuàng)建返回指向自身的 std::shared_ptr,避免不必要的重復(fù)引用計數(shù),從而有效地管理和共享對象生命周期。這樣既提升了代碼的安全性,也使得對象生命周期管理變得更加簡潔和直觀。
到此這篇關(guān)于C++ 智能指針使用不當(dāng)導(dǎo)致內(nèi)存泄漏問題的文章就介紹到這了,更多相關(guān)C++ 智能指針內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
重學(xué)c/c++之?dāng)?shù)據(jù)存儲詳解(整數(shù)、浮點數(shù))
C語言給定了一些基本的數(shù)據(jù)類型,下面這篇文章主要給大家介紹了關(guān)于重學(xué)c/c++之?dāng)?shù)據(jù)存儲(整數(shù)、浮點數(shù))的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
C++ Opencv imfill孔洞填充函數(shù)的實現(xiàn)思路與代碼
在Matlab下,使用imfill可以很容易的完成孔洞填充操作,下面這篇文章主要給大家介紹了關(guān)于C++ Opencv imfill孔洞填充函數(shù)的實現(xiàn)思路與代碼,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09

