shared_ptr相互嵌套導致循環引用
代碼示例
#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() {// 創建 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;
}
解釋說明
- 創建了兩個?
std::shared_ptr
?對象?a
?和?b
。 a
?持有?b
?的?shared_ptr
,b
?持有?a
?的?shared_ptr
。- 當?
main
?函數結束時,a
?和?b
?的引用計數不會減少到零,因此它們的析構函數不會被調用。 - 導致內存泄漏,因為對象?
A
?和?B
?的內存不會被釋放。
?解決方法
為了避免這種循環引用的問題,可以使用 std::weak_ptr
。std::weak_ptr
是一種弱智能指針,它不會增加對象的引用計數。它可以用來打破循環引用,從而防止內存泄漏。
#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_ptrif (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() {// 創建 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 的引用計數將會正確地減少到零,并且它們將會被銷毀。
}
shared_ptr的層次使用沒有導致循環引用
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>
。它們之間只是層次結構,沒有跨層次的相互引用?。也就是說沒有內存泄漏的問題。證明如下:
#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() {// 創建 jsFiles,它是一個 shared_ptr,指向 vectorauto 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;
}
同時也證明了一個結論,構造函數和析構函數的調用順序是相反的。?
回調函數中的循環引用問題
值捕獲
#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最后沒有調用析構函數,是shared_ptr循環引用了。
引用捕獲
如果換為引用捕獲,則不會造成?shared_ptr循環引用。雖然這種方式不會增加引用計數,但需要特別注意捕獲對象的生命周期,防止在 lambda 被調用時,對象已經被銷毀,從而導致未定義行為。
如何解決?
#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 捕獲,當 createNoLeak() 結束時,myObject 的生命周期也就結束了,并且引用計數=0
}int main() {createNoLeak();std::cout << "End of program" << std::endl;return 0;
}
weakPtr.lock()
?的使用:持有std::weak_ptr
,并且需要檢查或者使用其管理的對象。如果對象仍然存在(即它的shared_ptr
引用計數大于零),我們希望獲取一個shared_ptr
來安全地使用該對象。否則,weak_ptr.lock()
返回一個空的shared_ptr
。std::enable_shared_from_this
是一個非常有用的標準庫模板類,用于解決一個特定的問題: 當一個類的成員函數需要創建一個指向自己(this
)的std::shared_ptr
時,這類問題如何安全地實現。
std::enable_shared_from_this
背景問題
在使用 std::shared_ptr
管理對象時,有時會遇到需要在類的成員函數中獲取該對象的 shared_ptr
的情況。例如,在一個類的成員函數中,如果想要得到一個指向該對象的 shared_ptr
,不能簡單地使用 std::shared_ptr<MyClass>(this)
,因為這會創建一個新的 shared_ptr
,而不是增加現有的 shared_ptr
的引用計數。這可能導致對象被提前銷毀或者多次銷毀。
std::enable_shared_from_this
?的作用
通過繼承 std::enable_shared_from_this
,類就能夠安全地使用 shared_from_this
方法,從而獲取一個 shared_ptr
,該 shared_ptr
與其他 shared_ptr
共享所有權,而不會重復增加引用計數。
使用示例
#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; }// 一個成員函數,它需要返回一個指向自身的 shared_ptrstd::shared_ptr<MyClass> getSharedPtr() {// 使用 shared_from_this 返回一個 shared_ptrreturn shared_from_this();}void doSomething() {auto ptr = shared_from_this(); // 獲取 shared_ptrstd::cout << "Doing something with MyClass instance, ref count: " << ptr.use_count() << std::endl;}
};void exampleFunction() {// 創建 MyClass 對象的 shared_ptrauto myObject = std::make_shared<MyClass>();// 調用成員函數獲取 shared_ptrauto mySharedPtr = myObject->getSharedPtr();std::cout << "Reference count after getSharedPtr: " << mySharedPtr.use_count() << std::endl;myObject->doSomething();
}int main() {exampleFunction();return 0;
}
注意
1.創建對象:
只有通過?std::shared_ptr
?創建或管理的對象,才能安全地使用?shared_from_this
。
2. 保護避免使用 new
操作符:
直接使用 new
操作符創建的對象不能正確使用 shared_from_this
,這樣做可能會導致未定義行為(例如崩潰)。
為什么?std::enable_shared_from_this
?是必要的?
std::enable_shared_from_this
內部維護了一個弱引用(std::weak_ptr
)指向當前對象。這個弱引用確保不會增加引用計數,同時允許 shared_from_this
方法安全地獲取 std::shared_ptr
,從而真正共享管理的對象,避免不安全的重復引用計數增加。
通過這樣做,C++ STL 提供了一種方便而安全的方式來管理對象的生命周期,特別是在需要從對象內部生成 shared_ptr
的情境下。
總結
通過繼承 std::enable_shared_from_this
,MyClass
能夠安全地在其成員函數中創建返回指向自身的 std::shared_ptr
,避免不必要的重復引用計數,從而有效地管理和共享對象生命周期。這樣既提升了代碼的安全性,也使得對象生命周期管理變得更加簡潔和直觀。