智能指針(weak_ptr )之三

1. std::weak_ptr

1.1 定義與用法

std::weak_ptr 是一種不擁有對象所有權的智能指針,用于觀察但不影響對象的生命周期。主要用于解決 shared_ptr 之間的循環引用問題。

主要特性

  • 非擁有所有權:不增加引用計數。
  • 可從 shared_ptr 生成:通過 std::weak_ptr 可以訪問 shared_ptr 管理的對象。
  • 避免循環引用:適用于雙向關聯或觀察者模式。

1.2 避免循環引用

在存在雙向關聯(如父子關系)時,使用多個 shared_ptr 可能導致循環引用,導致內存泄漏。此時,可以使用 weak_ptr 來打破循環。

1.3 代碼案例

場景:帶有循環引用的觀察者模式(天氣預報系統)
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>// -------------------- 抽象接口部分 --------------------// 觀察者接口:所有 App 都要實現這個接口
class Observer {
public:virtual void update(float temp) = 0; // 被通知時調用virtual ~Observer() {std::cout << "Observer destroyed" << std::endl;}
};// 主題(被觀察者)接口:天氣預報系統實現它
class Subject {
public:virtual void attach(std::shared_ptr<Observer> observer) = 0; // 添加觀察者virtual void detach(std::shared_ptr<Observer> observer) = 0; // 移除觀察者virtual void notify() = 0;                                   // 通知所有觀察者virtual ~Subject() {std::cout << "Subject destroyed" << std::endl;}
};// -------------------- 主題實現部分 --------------------// 天氣預報系統,持有觀察者的 shared_ptr 列表
class WeatherStation : public Subject {
private:std::vector<std::shared_ptr<Observer>> observers; // 所有訂閱者float temperature = 0.0f;public:// 注冊觀察者void attach(std::shared_ptr<Observer> observer) override {observers.push_back(observer);}// 注銷觀察者void detach(std::shared_ptr<Observer> observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}// 通知所有觀察者,調用它們的 update()void notify() override {for (auto& obs : observers) {obs->update(temperature);}}// 模擬溫度變化,觸發通知void setTemperature(float t) {temperature = t;std::cout << "[WeatherStation] New temperature: " << t << "°C\n";notify();}~WeatherStation() {std::cout << "[WeatherStation] destroyed\n";}
};// -------------------- 觀察者實現部分 --------------------// App 實現觀察者接口:當溫度太高,會自動注銷
class AppClient : public Observer, public std::enable_shared_from_this<AppClient> {
private:std::shared_ptr<WeatherStation> station; // ? 持有 shared_ptr,形成循環引用public:// 構造函數,記錄主題指針AppClient(std::shared_ptr<WeatherStation> s) : station(s) {}// 被通知時調用void update(float temp) override {std::cout << "[AppClient] Received temperature: " << temp << "°C\n";// 如果溫度過高,就自動注銷if (temp > 35.0f) {std::cout << "[AppClient] Too hot! Unsubscribing...\n";// ? 使用 shared_from_this() 注銷自己,導致循環引用無法釋放station->detach(shared_from_this());}}// 析構函數~AppClient() {std::cout << "[AppClient] destroyed\n";}
};// -------------------- 主函數 --------------------int main() {// 創建主題對象(天氣系統)std::shared_ptr<WeatherStation> station = std::make_shared<WeatherStation>();// 創建觀察者對象(App),并訂閱std::shared_ptr<AppClient> app = std::make_shared<AppClient>(station);station->attach(app);// 第一次更新溫度,通知觀察者station->setTemperature(32.0f);// 第二次更新溫度,觀察者會自動注銷// station->setTemperature(36.5f);  //如果解除這一步,那么不會產生引用循環// 程序即將結束std::cout << "[main] Exiting...\n";return 0;
}

輸出

結果:產生引用循環Observer 和 Subject  沒有銷毀 導致內存泄漏[WeatherStation] New temperature: 32°C
[AppClient] Received temperature: 32°C
[main] Exiting...----------------------------------------------------------------------------//解除station->setTemperature(36.5f);				
結果:	不會產生引用循環[WeatherStation] New temperature: 32°C  
[AppClient] Received temperature: 32°C  
[WeatherStation] New temperature: 36.5°C
[AppClient] Received temperature: 36.5°C
[AppClient] Too hot! Unsubscribing...   
[main] Exiting...
[AppClient] destroyed
Observer destroyed
[WeatherStation] destroyed
Subject destroyed
解決方案:使用 weak_ptr

改用 weak_ptr 其中一方,打破循環引用。

方式一 把AppClient里面的station改為weak_ptr類型,其他操作也做出相應改變

//方式一 把AppClient里面的station改為weak_ptr類型,其他操作也做出相應改變
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>// -------------------- 抽象接口部分 --------------------// 觀察者接口:所有 App 都要實現這個接口
class Observer {
public:virtual void update(float temp) = 0; // 被通知時調用virtual ~Observer() {std::cout << "Observer destroyed" << std::endl;}
};// 主題(被觀察者)接口:天氣預報系統實現它
class Subject {
public:virtual void attach(std::shared_ptr<Observer> observer) = 0; // 添加觀察者virtual void detach(std::shared_ptr<Observer> observer) = 0; // 移除觀察者virtual void notify() = 0;                                   // 通知所有觀察者virtual ~Subject() {std::cout << "Subject destroyed" << std::endl;}
};// -------------------- 主題實現部分 --------------------// 天氣預報系統,持有觀察者的 shared_ptr 列表
class WeatherStation : public Subject {
private:std::vector<std::shared_ptr<Observer>> observers; // 所有訂閱者float temperature = 0.0f;public:// 注冊觀察者void attach(std::shared_ptr<Observer> observer) override {observers.push_back(observer);}// 注銷觀察者void detach(std::shared_ptr<Observer> observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}// 通知所有觀察者,調用它們的 update()void notify() override {for (auto& obs : observers) {obs->update(temperature);}}// 模擬溫度變化,觸發通知void setTemperature(float t) {temperature = t;std::cout << "[WeatherStation] New temperature: " << t << "°C\n";notify();}~WeatherStation() {std::cout << "[WeatherStation] destroyed\n";}
};// -------------------- 觀察者實現部分 --------------------// App 實現觀察者接口:當溫度太高,會自動注銷
class AppClient : public Observer, public std::enable_shared_from_this<AppClient> {
private:std::weak_ptr<WeatherStation> station;  //修改1public:// 構造函數,記錄主題指針AppClient(std::shared_ptr<WeatherStation> s) : station(s) {}// 被通知時調用void update(float temp) override {std::cout << "[AppClient] Received temperature: " << temp << "°C\n";// 如果溫度過高,就自動注銷if (temp > 35.0f) {std::cout << "[AppClient] Too hot! Unsubscribing...\n";if(!station.expired()) //修改2{station.lock()->detach(shared_from_this()); }}}// 析構函數~AppClient() {std::cout << "[AppClient] destroyed\n";}
};// -------------------- 主函數 --------------------int main() {// 創建主題對象(天氣系統)std::shared_ptr<WeatherStation> station = std::make_shared<WeatherStation>();// 創建觀察者對象(App),并訂閱std::shared_ptr<AppClient> app = std::make_shared<AppClient>(station);station->attach(app);// 第一次更新溫度,通知觀察者station->setTemperature(32.0f);// 第二次更新溫度,觀察者會自動注銷// station->setTemperature(36.5f);  //如果解除這一步,那么不會產生引用循環// 程序即將結束std::cout << "[main] Exiting...\n";return 0;
}

輸出

[WeatherStation] New temperature: 32°C
[AppClient] Received temperature: 32°C
[main] Exiting...
[WeatherStation] destroyed
[AppClient] destroyed
Observer destroyed
Subject destroyed

方式二 把WeatherStation里面的observers改為weak_ptr類型,其他操作也做出相應改變

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>// -------------------- 抽象接口部分 --------------------// 觀察者接口:所有 App 都要實現這個接口
class Observer
{
public:virtual void update(float temp) = 0; // 被通知時調用virtual ~Observer(){std::cout << "Observer destroyed" << std::endl;}
};// 主題(被觀察者)接口:天氣預報系統實現它
class Subject
{
public:virtual void attach(std::shared_ptr<Observer> observer) = 0; // 添加觀察者virtual void detach(std::shared_ptr<Observer> observer) = 0; // 移除觀察者virtual void notify() = 0;                                   // 通知所有觀察者virtual ~Subject(){std::cout << "Subject destroyed" << std::endl;}
};// -------------------- 主題實現部分 --------------------// 天氣預報系統,持有觀察者的 shared_ptr 列表
class WeatherStation : public Subject
{
private:std::vector<std::weak_ptr<Observer>> observers; // 修改1float temperature = 0.0f;public:// 注冊觀察者void attach(std::shared_ptr<Observer> observer) override{observers.push_back(observer);}// 注銷觀察者void detach(std::shared_ptr<Observer> observer) override{observers.erase(    // 修改2 std::remove_if(observers.begin(), observers.end(),[&observer](const std::weak_ptr<Observer> &wptr){return !wptr.expired() && wptr.lock() == observer;}),observers.end());}// void detach(std::shared_ptr<Observer> observer) override {//        std::cout << "[WeatherStation] detach\n";//        std::weak_ptr<Observer> temp = observer;//        observers.erase(std::remove(observers.begin(), observers.end(), temp), observers.end());//   }//如果改成這樣的話,思路很接近核心,看起來像是合理的,但它其實 仍然不行//原因就在于:std::weak_ptr 不支持 == 比較(哪怕是彼此之間),除非你先把它們 lock 成 shared_ptr//兩個 weak_ptr 即使指向相同的對象,它們也可能過期(expired)//無法確定“空的”和“已銷毀”的哪個該算“相等”// 通知所有觀察者,調用它們的 update()void notify() override{for (auto &obs : observers){if (!obs.expired()) // 修改3obs.lock()->update(temperature); }}// 模擬溫度變化,觸發通知void setTemperature(float t){temperature = t;std::cout << "[WeatherStation] New temperature: " << t << "°C\n";notify();}~WeatherStation(){std::cout << "[WeatherStation] destroyed\n";}
};// -------------------- 觀察者實現部分 --------------------// App 實現觀察者接口:當溫度太高,會自動注銷
class AppClient : public Observer, public std::enable_shared_from_this<AppClient>
{
private:std::shared_ptr<WeatherStation> station; // ? 持有 shared_ptr,形成循環引用public:// 構造函數,記錄主題指針AppClient(std::shared_ptr<WeatherStation> s) : station(s) {}// 被通知時調用void update(float temp) override{std::cout << "[AppClient] Received temperature: " << temp << "°C\n";// 如果溫度過高,就自動注銷if (temp > 35.0f){std::cout << "[AppClient] Too hot! Unsubscribing...\n";// ? 使用 shared_from_this() 注銷自己,導致循環引用無法釋放station->detach(shared_from_this());}}// 析構函數~AppClient(){std::cout << "[AppClient] destroyed\n";}
};// -------------------- 主函數 --------------------int main()
{// 創建主題對象(天氣系統)std::shared_ptr<WeatherStation> station = std::make_shared<WeatherStation>();// 創建觀察者對象(App),并訂閱std::shared_ptr<AppClient> app = std::make_shared<AppClient>(station);station->attach(app);// 第一次更新溫度,通知觀察者station->setTemperature(32.0f);// 第二次更新溫度,觀察者會自動注銷// station->setTemperature(36.5f);  //如果解除這一步,那么不會產生引用循環// 程序即將結束std::cout << "[main] Exiting...\n";return 0;
}

輸出

[WeatherStation] New temperature: 32°C
[AppClient] Received temperature: 32°C
[main] Exiting...
[AppClient] destroyed
Observer destroyed
[WeatherStation] destroyed
Subject destroyed

1.4 訪問 weak_ptr 指向的對象

weak_ptr 不能直接訪問對象,需要通過 lock() 方法轉換為 shared_ptr,并檢查對象是否仍然存在。

  • std::weak_ptr::expired()
    • 功能:std::weak_ptr 的 expired() 成員函數用于檢查 std::weak_ptr 所引用的對象是否已經被銷毀(即關聯的 std::shared_ptr 的引用計數是否已變為 0)。
    • 返回值:
      • bool類型
        • true:表示 std::weak_ptr 已過期(引用的對象已被銷毀)。
        • false:表示 std::weak_ptr 未過期(引用的對象仍然存在)。
#include <iostream>
#include <memory>int main() {std::shared_ptr<int> sp = std::make_shared<int>(42);std::weak_ptr<int> wp = sp;if (auto locked = wp.lock()) { //wp.lock() 會嘗試安全地獲取一個新的 shared_ptr,如果資源還存在,它就返回一個有效的 								 //shared_ptr,否則返回一個空的 shared_ptr 到時if(shared_ptr)會觸發operator bool()函								 //	數,也就是if(shared_ptr.bool()) == if(return shared_ptr.get() != nullptr;)std::cout << "Value: " << *locked << std::endl;} else {std::cout << "Object no longer exists." << std::endl;}sp.reset(); // 釋放資源if (auto locked = wp.lock()) { // 再次嘗試獲取 shared_ptr std::cout << "Value: " << *locked << std::endl;} else {std::cout << "Object no longer exists." << std::endl;}/*	效果一樣!expired()if(!wp.expired()) { // 再次嘗試獲取 shared_ptr std::cout << "Value: " << *wp.lock() << std::endl;} else {std::cout << "Object no longer exists." << std::endl;}*/return 0;
}

輸出

Value: 42
Object no longer exists.

解析

  • wp.lock() 返回一個 shared_ptr,如果對象依然存在,則有效。
  • sp.reset() 釋放資源后,wp.lock() 無法獲取有效的 shared_ptr

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/77871.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77871.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/77871.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

學習海康VisionMaster之卡尺工具

一&#xff1a;進一步學習了 今天學習下VisionMaster中的卡尺工具&#xff1a;主要用于測量物體的寬度、邊緣的特征的位置以及圖像中邊緣對的位置和間距 二&#xff1a;開始學習 1&#xff1a;什么是卡尺工具&#xff1f; 如果我需要檢測芯片的每一個PIN的寬度和坐標&#xff…

Java面試實戰:從Spring Boot到微服務的深入探討

Java面試實戰&#xff1a;從Spring Boot到微服務的深入探討 場景&#xff1a;電商場景的面試之旅 在某互聯網大廠的面試間&#xff0c;面試官李老師正襟危坐&#xff0c;而對面坐著的是傳說中的“水貨程序員”趙大寶。 第一輪&#xff1a;核心Java與構建工具 面試官&#x…

深入理解 Spring @Configuration 注解

在 Spring 框架中,@Configuration 注解是一個非常重要的工具,它用于定義配置類,這些類可以包含 Bean 定義方法。通過使用 @Configuration 和 @Bean 注解,開發者能夠以編程方式創建和管理應用程序上下文中的 Bean。本文將詳細介紹 @Configuration 注解的作用、如何使用它以及…

密碼學中的鹽值是什么?

目錄 1. 鹽值的基本概念 2. 鹽值的作用 (1) 防止彩虹表攻擊 (2) 防止相同的密碼生成相同的哈希值 (3) 增加暴力破解的難度 3. 如何使用鹽值&#xff1f; (1) 生成鹽值 (2) 將鹽值附加到密碼 (3) 存儲鹽值和哈希值 (4) 驗證密碼 4. 鹽值如何增加暴力破解的難度 在線暴…

基于瑞芯微RK3576國產ARM八核2.2GHz A72 工業評估板——Docker容器部署方法說明

前 言 本文適用開發環境: Windows開發環境:Windows 7 64bit、Windows 10 64bit Linux開發環境:VMware16.2.5、Ubuntu22.04.5 64bit U-Boot:U-Boot-2017.09 Kernel:Linux-6.1.115 LinuxSDK:LinuxSDK-[版本號](基于rk3576_linux6.1_release_v1.1.0) Docker是一個開…

大數據技術全解析

目錄 前言1. Kafka&#xff1a;流數據的傳輸平臺1.1 Kafka概述1.2 Kafka的應用場景1.3 Kafka的特點 2. HBase&#xff1a;分布式列式數據庫2.1 HBase概述2.2 HBase的應用場景2.3 HBase的特點 3. Hadoop&#xff1a;大數據處理的基石3.1 Hadoop概述3.2 Hadoop的應用場景3.3 Hado…

mcpo的簡單使用

1.安裝依賴 conda create -n mcpo python3.11 conda activate mcpo pip install mcpo pip install uv2.隨便從https://github.com/modelcontextprotocol/servers?tabreadme-ov-file 找一個mcp服務使用就行&#xff0c;我這里選的是爬蟲 然后安裝 pip install mcp-server-f…

uniapp-商城-32-shop 我的訂單-訂單詳情和組件goods-list

上面完成了我的訂單&#xff0c;通過點擊我的訂單中每一條數據&#xff0c;可以跳轉到訂單詳情中。 這里就需要展示訂單的狀態&#xff0c;支付狀態&#xff0c;物流狀態&#xff0c;取貨狀態&#xff0c;用戶信息&#xff0c;訂單中的貨物詳情等。 1、創建一個訂單詳情文件 …

XCVU13P-2FHGA2104I Xilinx Virtex UltraScale+ FPGA

XCVU13P-2FHGA2104I 是 Xilinx&#xff08;現為 AMD&#xff09;Virtex UltraScale? FPGA 系列中的高端 Premium 器件&#xff0c;基于 16nm FinFET 工藝并采用 3D IC 堆疊硅互連&#xff08;SSI&#xff09;技術&#xff0c;提供業內頂級的計算密度和帶寬?。該芯片集成約 3,…

【Python3】Django 學習之路

第一章&#xff1a;Django 簡介 1.1 什么是 Django&#xff1f; Django 是一個高級的 Python Web 框架&#xff0c;旨在讓 Web 開發變得更加快速和簡便。它鼓勵遵循“不要重復自己”&#xff08;DRY&#xff0c;Don’t Repeat Yourself&#xff09;的原則&#xff0c;并提供了…

Python 設計模式:模板模式

1. 什么是模板模式&#xff1f; 模板模式是一種行為設計模式&#xff0c;它定義了一個操作的算法的骨架&#xff0c;而將一些步驟延遲到子類中。模板模式允許子類在不改變算法結構的情況下&#xff0c;重新定義算法的某些特定步驟。 模板模式的核心思想是將算法的固定部分提取…

【后端】構建簡潔的音頻轉寫系統:基于火山引擎ASR實現

在當今數字化時代&#xff0c;語音識別技術已經成為許多應用不可或缺的一部分。無論是會議記錄、語音助手還是內容字幕&#xff0c;將語音轉化為文本的能力對提升用戶體驗和工作效率至關重要。本文將介紹如何構建一個簡潔的音頻轉寫系統&#xff0c;專注于文件上傳、云存儲以及…

音頻base64

音頻 Base64 是一種將二進制音頻數據&#xff08;如 MP3、WAV 等格式&#xff09;編碼為 ASCII 字符串的方法。通過 Base64 編碼&#xff0c;音頻文件可以轉換為純文本形式&#xff0c;便于在文本協議&#xff08;如 JSON、XML、HTML 或電子郵件&#xff09;中傳輸或存儲&#…

240422 leetcode exercises

240422 leetcode exercises jarringslee 文章目錄 240422 leetcode exercises[237. 刪除鏈表中的節點](https://leetcode.cn/problems/delete-node-in-a-linked-list/)&#x1f501;節點覆蓋法 [392. 判斷子序列](https://leetcode.cn/problems/is-subsequence/)&#x1f501;…

MYSQL之庫的操作

創建數據庫 語法很簡單, 主要是看看選項(與編碼相關的): CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification: [DEFAULT] CHARACTER SET charset_name [DEFAULT] COLLATE collation_name 1. 語句中大寫的是…

Git Flow分支模型

經典分支模型(Git Flow) 由 Vincent Driessen 提出的 Git Flow 模型,是管理 main(或 master)和 dev 分支的經典方案: main 用于生產發布,保持穩定; dev 用于日常開發,合并功能分支(feature/*); 功能開發在 feature 分支進行,完成后合并回 dev; 預發布分支(rele…

【Spring】依賴注入的方式:構造方法、setter注入、字段注入

在Spring框架中&#xff0c;除了構造器注入&#xff08;Constructor Injection&#xff09;和Setter注入&#xff08;Setter Injection&#xff09;&#xff0c;還有一種依賴注入方式&#xff1a;字段注入&#xff08;Field Injection&#xff09;。字段注入通過在Bean的字段上…

【數學建模】隨機森林算法詳解:原理、優缺點及應用

隨機森林算法詳解&#xff1a;原理、優缺點及應用 文章目錄 隨機森林算法詳解&#xff1a;原理、優缺點及應用引言隨機森林的基本原理隨機森林算法步驟隨機森林的優點隨機森林的缺點隨機森林的應用場景Python實現示例超參數調優結論參考文獻 引言 隨機森林是機器學習領域中一種…

HttpSessionListener 的用法筆記250417

HttpSessionListener 的用法筆記250417 以下是關于 HttpSessionListener 的用法詳解&#xff0c;涵蓋核心方法、實現步驟、典型應用場景及注意事項&#xff0c;幫助您全面掌握會話&#xff08;Session&#xff09;生命周期的監聽與管理&#xff1a; 1. 核心功能 HttpSessionLi…

【Python爬蟲基礎篇】--2.模塊解析

目錄 1.urllib庫 1.1.request模塊 1.1.1、urllib.request.urlopen() 函數 1.1.2.urllib.request.urlretrieve() 函數 1.2. error模塊 1.3. parse 模塊 2. BeautifulSoup4庫 2.1.對象種類 2.2.對象屬性 2.2.1.子節點 2.2.2.父節點 2.2.3.兄弟節點 2.2.4.回退和前進 …