C++中智能指針weak_ptr的原理及使用
1. weak_ptr 的基本概念
weak_ptr
是 C++11 引入的一種智能指針,它與 shared_ptr
配合使用,主要解決以下問(wèn)題:
- 打破循環(huán)引用:防止
shared_ptr
之間的循環(huán)引用導(dǎo)致內(nèi)存泄漏 - 安全觀察:允許觀察共享對(duì)象而不影響其生命周期
- 避免懸空指針:可以檢測(cè)被觀察對(duì)象是否已被釋放
2. weak_ptr 的核心原理
2.1 控制塊結(jié)構(gòu)
weak_ptr
與 shared_ptr
共享同一個(gè)控制塊,控制塊包含:
struct ControlBlock { std::atomic<size_t> shared_count; // 強(qiáng)引用計(jì)數(shù) std::atomic<size_t> weak_count; // 弱引用計(jì)數(shù) // 其他元數(shù)據(jù)(刪除器、分配器等) };
2.2 內(nèi)存管理規(guī)則
- 對(duì)象銷毀條件:當(dāng)
shared_count
歸零時(shí),管理對(duì)象被銷毀 - 控制塊銷毀條件:當(dāng)
shared_count
和weak_count
都?xì)w零時(shí),控制塊被釋放 - weak_ptr 不參與所有權(quán):僅增加
weak_count
,不影響shared_count
2.3 工作流程
// 創(chuàng)建 shared_ptr(控制塊:shared=1, weak=0) auto sp = std::make_shared<int>(42); // 創(chuàng)建 weak_ptr(控制塊:shared=1, weak=1) std::weak_ptr<int> wp = sp; // shared_ptr 析構(gòu)(控制塊:shared=0, weak=1) sp.reset(); // 此時(shí): // - 管理的 int 對(duì)象已被銷毀 // - 控制塊仍然存在(weak_count=1) // - wp.expired() == true // weak_ptr 析構(gòu)(控制塊:shared=0, weak=0) // 控制塊被釋放
3. weak_ptr 的關(guān)鍵操作
3.1 創(chuàng)建與賦值
// 從 shared_ptr 創(chuàng)建 auto sp = std::make_shared<MyClass>(); std::weak_ptr<MyClass> wp1(sp); // 拷貝構(gòu)造 std::weak_ptr<MyClass> wp2(wp1); // 賦值操作 std::weak_ptr<MyClass> wp3; wp3 = wp1;
3.2 檢查與升級(jí)
// 檢查對(duì)象是否有效 if (!wp.expired()) { // 嘗試升級(jí)為 shared_ptr if (auto sp = wp.lock()) { // 使用 sp 安全訪問(wèn)對(duì)象 } }
3.3 資源釋放監(jiān)控
// 監(jiān)控資源釋放 std::weak_ptr<MyClass> wp; { auto sp = std::make_shared<MyClass>(); wp = sp; // 對(duì)象存在 } // 此時(shí) wp.expired() == true
4. weak_ptr 的線程安全性
4.1 官方標(biāo)準(zhǔn)規(guī)定
- 控制塊操作是原子的:
weak_count
的增減是線程安全的 - lock() 操作是線程安全的:升級(jí)為
shared_ptr
的過(guò)程是原子的 - 對(duì)象訪問(wèn)需要同步:通過(guò)
lock()
獲取的shared_ptr
需要額外同步
4.2 線程安全示例
std::shared_ptr<int> sp = std::make_shared<int>(42); std::weak_ptr<int> wp(sp); // 線程1 if (auto local_sp = wp.lock()) { std::lock_guard<std::mutex> lock(mtx); *local_sp = 10; } // 線程2 if (auto local_sp = wp.lock()) { std::lock_guard<std::mutex> lock(mtx); int val = *local_sp; }
5. weak_ptr 的典型應(yīng)用場(chǎng)景
5.1 打破循環(huán)引用
class Parent { std::shared_ptr<Child> child; }; class Child { std::weak_ptr<Parent> parent; // 使用 weak_ptr 避免循環(huán) };
5.2 緩存系統(tǒng)
class Cache { std::unordered_map<Key, std::weak_ptr<Resource>> cache; std::shared_ptr<Resource> get(Key key) { if (auto it = cache.find(key); it != cache.end()) { if (auto sp = it->second.lock()) { return sp; // 緩存命中 } cache.erase(it); // 清理過(guò)期緩存 } // 緩存未命中,創(chuàng)建新資源 auto sp = std::make_shared<Resource>(key); cache[key] = sp; return sp; } };
5.3 觀察者模式
class Subject { std::vector<std::weak_ptr<Observer>> observers; void notify() { for (auto it = observers.begin(); it != observers.end(); ) { if (auto obs = it->lock()) { obs->update(); ++it; } else { it = observers.erase(it); // 移除無(wú)效觀察者 } } } };
6. weak_ptr 的實(shí)現(xiàn)細(xì)節(jié)
6.1 控制塊生命周期
6.2 lock() 的原子實(shí)現(xiàn)
lock()
操作必須保證線程安全,偽代碼實(shí)現(xiàn):
shared_ptr<T> lock() const noexcept { ControlBlock* cb = get_control_block(); size_t sc = cb->shared_count.load(); do { if (sc == 0) return nullptr; // 對(duì)象已釋放 // 嘗試增加 shared_count(CAS操作) } while (!cb->shared_count.compare_exchange_weak(sc, sc + 1)); return shared_ptr<T>(cb); // 創(chuàng)建新的 shared_ptr }
7. weak_ptr 的注意事項(xiàng)
不能直接解引用:必須先用 lock()
升級(jí)為 shared_ptr
// 錯(cuò)誤用法 // *wp; // 編譯錯(cuò)誤 // 正確用法 if (auto sp = wp.lock()) { *sp = value; }
性能考慮:
lock()
操作包含原子操作,有一定開(kāi)銷- 控制塊需要額外內(nèi)存(通常16-32字節(jié))
構(gòu)造函數(shù)限制:
// 不能直接從裸指針構(gòu)造 // std::weak_ptr<int> wp(new int(42)); // 錯(cuò)誤 // 必須從 shared_ptr 構(gòu)造 auto sp = std::make_shared<int>(42); std::weak_ptr<int> wp(sp); // 正確
8. 性能優(yōu)化建議
避免頻繁 lock/unlock:在需要時(shí)緩存 shared_ptr
void process(std::weak_ptr<Data> wp) { auto sp = wp.lock(); // 只調(diào)用一次 lock if (!sp) return; // 多次使用 sp 而不重復(fù)調(diào)用 lock sp->operation1(); sp->operation2(); }
及時(shí)清理失效 weak_ptr:定期檢查并移除 expired()
的 weak_ptr
考慮使用 make_shared:對(duì)象和控制塊單次分配,減少內(nèi)存碎片
9. 與其它智能指針的對(duì)比
特性 | weak_ptr | shared_ptr | unique_ptr |
---|---|---|---|
所有權(quán) | 無(wú) | 共享 | 獨(dú)占 |
影響生命周期 | 否 | 是 | 是 |
直接訪問(wèn)對(duì)象 | 需通過(guò) lock() | 可直接訪問(wèn) | 可直接訪問(wèn) |
引用計(jì)數(shù) | 只增加 weak_count | 增加 shared_count | 無(wú) |
典型用途 | 打破循環(huán)引用/觀察 | 共享所有權(quán) | 獨(dú)占所有權(quán) |
10. 現(xiàn)代 C++ 中的增強(qiáng)
10.1 C++17 的 weak_from_this
class MyClass : public std::enable_shared_from_this<MyClass> { public: std::weak_ptr<MyClass> get_weak() { return weak_from_this(); // 安全獲取 weak_ptr } }; auto obj = std::make_shared<MyClass>(); auto wobj = obj->get_weak(); // 安全獲取 weak_ptr
10.2 C++20 的原子 shared_ptr/weak_ptr
std::atomic<std::weak_ptr<int>> atomic_wp; atomic_wp.store(wp, std::memory_order_release); auto current = atomic_wp.load(std::memory_order_acquire);
11. 總結(jié)
weak_ptr
的核心原理可以總結(jié)為:
- 不參與所有權(quán):僅觀察不擁有,不影響對(duì)象生命周期
- 共享控制塊:與
shared_ptr
共享同一控制塊,維護(hù)weak_count
- 安全升級(jí):通過(guò)
lock()
原子操作獲取可用的shared_ptr
- 自動(dòng)清理:當(dāng)最后一個(gè)
weak_ptr
析構(gòu)后,控制塊被釋放
正確使用 weak_ptr
可以:
- 有效解決循環(huán)引用導(dǎo)致的內(nèi)存泄漏
- 實(shí)現(xiàn)安全的對(duì)象觀察模式
- 構(gòu)建高效的緩存系統(tǒng)
- 開(kāi)發(fā)更健壯的異步代碼
理解 weak_ptr
的工作原理對(duì)于設(shè)計(jì)復(fù)雜的內(nèi)存管理系統(tǒng)和避免資源泄漏至關(guān)重要,它是現(xiàn)代 C++ 高效內(nèi)存管理工具鏈中不可或缺的一環(huán)。
到此這篇關(guān)于C++中智能指針weak_ptr的原理及使用的文章就介紹到這了,更多相關(guān)C++ weak_ptr內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++雙向鏈表操作示例(創(chuàng)建雙向鏈、雙向鏈表中查找數(shù)據(jù)、插入數(shù)據(jù)等)
這篇文章主要介紹了c++雙向鏈表操作示例,包括創(chuàng)建雙向鏈、刪除雙向鏈表、雙向鏈表中查找數(shù)據(jù)、插入數(shù)據(jù)等,需要的朋友可以參考下2014-05-05C語(yǔ)言實(shí)現(xiàn)掃雷游戲及其優(yōu)化
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)掃雷游戲及其優(yōu)化,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08如何查看進(jìn)程實(shí)際的內(nèi)存占用情況詳解
本篇文章是對(duì)如何查看進(jìn)程實(shí)際的內(nèi)存占用情況進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之學(xué)生信息管理系統(tǒng)課程設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之學(xué)生信息管理系統(tǒng)課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11VC編程控件類HTControl之CHTGDIManager GDI資源管理類用法解析
這篇文章主要介紹了VC編程控件類HTControl之CHTGDIManager GDI資源管理類用法解析,需要的朋友可以參考下2014-08-08C# CLR 中學(xué)習(xí) C++關(guān)鍵詞extern使用詳解
這篇文章主要為大家介紹了C# CLR 中學(xué)習(xí) C++ 之extern使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09詳解C語(yǔ)言的exp()函數(shù)和ldexp()函數(shù)以及frexp()函數(shù)
這篇文章主要介紹了詳解C語(yǔ)言的exp()函數(shù)和ldexp()函數(shù)以及frexp()函數(shù),注意這三個(gè)函數(shù)雖然看起來(lái)相似但實(shí)際功能卻大相徑庭!需要的朋友可以參考下2015-08-08ros項(xiàng)目調(diào)試:vscode下配置開(kāi)發(fā)ROS項(xiàng)目的詳細(xì)教程
這篇文章主要介紹了ros項(xiàng)目調(diào)試:vscode下配置開(kāi)發(fā)ROS項(xiàng)目,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08