掌握C++回調:按值捕獲、按引用捕獲與弱引用

文章目錄

    • 一、按引用捕獲和按值捕獲
      • 1.1 原理
      • 1.2 案例
    • 二、弱引用
      • 2.1 原理
      • 2.2 案例一
      • 2.3 案例二:使用`base`庫的弱引用
    • 三、總結

在C++回調中,當使用Lambda表達式捕獲外部變量時,有兩種捕獲方式:按值捕獲和按引用捕獲。

一、按引用捕獲和按值捕獲

1.1 原理

  • 按引用捕獲是將外部變量的引用存儲在Lambda表達式的閉包中,[&] 表示按引用捕獲所有外部變量。這樣,當Lambda表達式執行時,它將直接訪問原始變量。這種方式在某些情況下可能導致問題,例如,當回調執行時,原始變量已經失效(例如,原始變量是棧上的局部變量,而回調在該變量離開作用域后執行)。

  • 按值捕獲是將外部變量的值復制到Lambda表達式的閉包中。這樣,當Lambda表達式執行時,它將使用這個復制的值,而不是原始變量的值。這種方式可以避免在回調執行時,原始變量已經失效的問題。

1.2 案例

原理雖然很簡單,但是當我們處于復雜的業務代碼中時,仍然不免會寫出bug。下面是筆者遇到的一個真實案例:

    std::string WebProxyKeysHelper::GetAuthCode(std::string &st_or_code, std::string &last_key) {...auto ph = (st_or_code == KEY_TYPE_OF_ST_STR ? RefreshProxySt() : RefreshOauthCode());auto prom_ptr = std::make_shared<std::promise<std::string>>();std::future<std::string> fut_pb = prom_ptr->get_future();ph.then([&, prom_ptr](bool ret) {std::string tmp_key = "";if (ret) {tmp_key = st_or_code == KEY_TYPE_OF_ST_STR ? proxy_st_ : oauth_code_;UpdateKeys(st_or_code, tmp_key);Schedule();}prom_ptr->set_value(tmp_key);}).onError([&, prom_ptr](const std::exception& ex){prom_ptr->set_value("");});}...return current_key;}

在上述代碼中,WebProxyKeysHelper::GetAuthCode 函數通過異步操作 ph 獲取代理密鑰。然后,根據異步操作的結果,回調函數更新密鑰并設置 prom_ptr 的值。然而,這段代碼存在一個潛在的問題,即在回調函數中使用了按引用捕獲的 st_or_code 變量。

問題在于,當 ph.then([&, prom_ptr](bool ret) { ... }) 回調執行時,st_or_code 變量可能已經離開了作用域并被銷毀。這會導致程序偶現閃退,也可能導致數值異常,最終表現為業務邏輯異常,因為回調函數試圖訪問一個已經失效的棧變量。

修改的方式是,將 st_or_code 變量改為按值捕獲。這樣,在回調執行時,即使原始的 st_or_code 變量離開了作用域,回調中仍然可以安全地使用其復制的值。下面是修正后的代碼:

    std::string WebProxyKeysHelper::GetAuthCode(std::string &st_or_code, std::string &last_key) {...auto ph = (st_or_code == KEY_TYPE_OF_ST_STR ? RefreshProxySt() : RefreshOauthCode());auto prom_ptr = std::make_shared<std::promise<std::string>>();std::future<std::string> fut_pb = prom_ptr->get_future();ph.then([&, st_or_code, prom_ptr](bool ret) { // 注意這里改為按值捕獲 st_or_codestd::string tmp_key = "";if (ret) {tmp_key = st_or_code == KEY_TYPE_OF_ST_STR ? proxy_st_ : oauth_code_;UpdateKeys(st_or_code, tmp_key);Schedule();}prom_ptr->set_value(tmp_key);}).onError([&, prom_ptr](const std::exception& ex){prom_ptr->set_value("");});}...return current_key;
}

二、弱引用

2.1 原理

弱引用(Weak Reference)是一種特殊的引用類型,它不會阻止其所引用的對象被垃圾回收。這在處理回調和長時間運行的任務時非常有用,因為它可以避免因為回調導致的潛在內存泄漏。

2.2 案例一

錯誤的寫法:

class Foo {
public:void start() {std::thread t([this]() {std::this_thread::sleep_for(std::chrono::seconds(1));this->doSomething();  // Undefined behavior if `this` is destroyed!});t.detach();}void doSomething() {std::cout << "Doing something..." << std::endl;}
};

在上述代碼中,我們在新線程中訪問了this指針。然而,如果新線程開始執行時,this指針所指向的對象已經被銷毀,這將導致未定義的行為。

正確的寫法:

class Foo : public std::enable_shared_from_this<Foo> {
public:void start() {std::thread t([weak_this = std::weak_ptr<Foo>(shared_from_this())]() {if (auto shared_this = weak_this.lock()) {shared_this->doSomething();  // 安全,只要 `this` 沒有被銷毀}});t.detach();}void doSomething() {std::cout << "Doing something..." << std::endl;}
};

在修正的代碼中,我們使用了弱引用來捕獲this指針。這樣,即使原始對象被銷毀,新線程中也不會訪問到無效的this指針。

2.3 案例二:使用base庫的弱引用

base::BindLambda(base::AsWeakPtr(this), [&] { ... }) 使用了弱引用。這里,base::AsWeakPtr(this)this指針轉換為弱引用,并將其傳遞給Lambda表達式。這樣,在回調執行時,如果this指針所指向的對象已經被銷毀,回調將不會執行,從而避免了潛在的內存泄漏問題。

下面是執行CGI任務時的回調寫法。當CGI網絡請求回來時,所在的Service類可能已經被析構,所以需要使用base::AsWeakPtr(this)this指針轉換為弱引用:

task->SetCallback(base::BindLambda(base::AsWeakPtr(this), [=](network::ProtocolErrorCode pec, const CRTX_WWK::BatchSetLeaderRsp& resp) {LogicErrorCode code = (pec == network::PEC_OK && task->response_head()->errcode() == 0) ? LEC_OK : LEC_ERROR;if(code == LEC_OK) {...}if (!callback.is_null()) callback.Run(code);
})); 
ScheduleTask(task.get());

大家可能已經注意到,上面的Lamda回調中,我們不需要再額外判斷this是否已經被析構,因為base庫已經替我們提前判斷好再回調:

/*** @brief BindLambda 函數實現了便捷的通過 C++ Lambda 表達式來創建 base::Callback 的方法。* 這個重載允許額外傳入一個 base::WeakPtr 類型的弱引用,在實際執行 functor 前會檢查弱引用的有效性,如果弱引用已經無效,則不會執行 functor。** @param weakptr 額外傳遞一個弱引用,在 functor 執行前會進行檢查,如果該弱引用無效則不會繼續調用 functor* @param functor C++ Lambda 表達式* @param params 需要綁定在 Lambda 表達式上的參數** @note 可根據實際情況,選擇使用捕獲或者綁定的方式傳遞參數。*/
template <typename SupportWeakPtrType, typename Functor, typename ...Params>
auto BindLambda(const WeakPtr<SupportWeakPtrType>& weakptr, const Functor& functor, const Params&... params) -> decltype(BindLambda(functor, params...)) {return _WrapWeakCallback(BindLambda(functor, params...), weakptr);
}template <typename SupportWeakPtrType, typename RetType, typename ...Params>
base::Callback<RetType(Params...)> _WrapWeakCallback(const base::Callback<RetType(Params...)>& callback, const WeakPtr<SupportWeakPtrType>& weakptr) {return base::Bind(&_RunWeakCallbackInternalRet<SupportWeakPtrType, RetType, Params...>, weakptr, callback);
}template <typename SupportWeakPtrType, typename RetType, typename ...Params>
RetType _RunWeakCallbackInternalRet(const WeakPtr<SupportWeakPtrType>& weakptr, const base::Callback<RetType(Params...)>& callback, Params... params) {if (weakptr.get()) {return callback.Run(params...);}return RetType();
}
  1. BindLambda 函數接受一個弱引用(weakptr)、一個Lambda表達式(functor)和一些參數(params)。它將創建一個回調函數,該回調在執行前會檢查弱引用的有效性。如果弱引用無效,則不會執行Lambda表達式。

  2. _WrapWeakCallback 函數接受一個回調函數(callback)和一個弱引用(weakptr)。它將創建一個新的回調函數,該回調函數在調用之前會檢查弱引用的有效性。

  3. _RunWeakCallbackInternalRet 函數在弱引用有效時執行回調函數(callback),否則返回默認值。這個函數實際上是在執行回調之前檢查弱引用的有效性的地方。

三、總結

在C++回調中,我們需要根據具體情況選擇合適的捕獲方式(按值捕獲、按引用捕獲或弱引用)。在處理回調和長時間運行的任務時,為了避免內存泄漏和訪問無效變量的問題,我們通常需要使用按值捕獲和弱引用。

最后我們總結一下本文:

類型原理注意事項
按值捕獲將外部變量的值復制到Lambda表達式的閉包中,使得Lambda表達式在執行時使用的是復制的值,而不是原始變量的值。如果捕獲的變量在Lambda表達式執行時已經離開了作用域,那么按值捕獲就是安全的,因為Lambda表達式中使用的是變量的副本。
按引用捕獲將外部變量的引用存儲在Lambda表達式的閉包中,使得Lambda表達式在執行時直接訪問的是原始變量。如果捕獲的變量在Lambda表達式執行時已經離開了作用域,那么按引用捕獲就可能導致未定義的行為。因此,使用按引用捕獲時,需要確保捕獲的變量在Lambda表達式執行時仍然有效。
弱引用弱引用是一種特殊的引用類型,它不會阻止其所引用的對象被垃圾回收。這在處理回調和長時間運行的任務時非常有用,因為它可以避免因為回調導致的潛在內存泄漏。如果弱引用所引用的對象在回調執行時已經被銷毀,那么回調將不會執行,從而避免了潛在的內存泄漏問題。因此,使用弱引用時,需要確保在回調執行時,弱引用所引用的對象仍然存在。

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

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

相關文章

Matlab自學筆記三十:元胞數組的修改、添加、刪除和連接

1.說明 元胞數組的子數組或元素也是元胞型的&#xff0c;其元素內容&#xff08;值&#xff09;是本身類型&#xff0c;因此&#xff0c;在添、刪、改和連接處理時&#xff0c;必須明確每個元素的值的類型和大小&#xff0c;否則&#xff0c;編程報錯是不可避免的了。看本文前…

Python 點云裁剪

點云裁剪 一、介紹1.1 概念1.2 函數講解二、代碼示例2.1 代碼實現2.2 代碼講解三、結果示例一、介紹 1.1 概念 點云裁剪 :根據待裁剪對象的多邊形體積(json文件)實現點云的裁剪。 1.2 函數講解 下面代碼示例中主要用到了兩個函數。 讀取待裁剪對象的多邊形體積信息(json文…

淺談C++函數

目錄 一、函數的概念二、調用函數的兩個前提三、函數傳參的三種形式四、函數返回類型 一、函數的概念 函數是C程序的基本模塊&#xff0c;通常一個C程序由一個或多個函數組成。函數可以完成用戶指定的任務&#xff0c;一般分為庫函數和用戶自定義的函數。函數由函數頭和函數體…

先進制造aps專題六 aps軟件開發最大的難點,設備甘特圖開發

aps軟件開發最大的難點&#xff0c;設備甘特圖開發 一般認為&#xff0c;aps軟開發中&#xff0c;算法是難的&#xff0c;排程算法難&#xff0c;優化算法更難&#xff0c;但其實最大的難點是設備甘特圖開發 aps軟件設備甘特圖開發的幾個難點如下 1 和項目甘特圖一行顯示一個…

02. Flink 快速上手

02. Flink 快速上手 1、創建項目導入依賴 pom文件&#xff1a; <properties><flink.version>1.17.0</flink.version> </properties><dependency><groupId>org.apache.flink</groupId><artifactId>flink-streaming-java<…

k8s配置pods滾動發布

背景 采用微服務架構部署的應用&#xff0c;部署方式都要用到容器化部署k8s容器編排&#xff0c;最近我在公司負載的系統也是用的上述架構部署&#xff0c;但是隨著系統的運行&#xff0c;用戶提的需求就會越多&#xff0c;每次更新的話都要停機發布&#xff0c;最用戶側來說就…

【C語言刷題系列】求一個數組中兩個元素a和b的和最接近整數m

&#x1f493; 博客主頁&#xff1a;倔強的石頭的CSDN主頁 &#x1f4dd;Gitee主頁&#xff1a;倔強的石頭的gitee主頁 ? 文章專欄&#xff1a;C語言刷題系列 目錄 一、問題描述 二、解題思路 解題思路&#xff1a; 解題步驟: 三、C語言代碼實現及測試 一、問題描述 給定一…

指北者智能音樂學習機隆重亮相廣州國際樂器展

2024年5月23-26日廣州國際樂器展覽會在廣交會展館B區隆重開幕&#xff0c;本屆展會開設5大展廳、50000平方米的主題展區&#xff0c;吸引了700多家國內外參展商參展&#xff0c;打造集展示、商貿、文化交流、文娛于一體的廣闊平臺。深圳市指北科技有限公司也攜旗下品牌指北者智…

AWS云服務器每月費用高昂,如何優化達到節省目的?

AWS云服務器每月費用可能因不同的使用情況和配置而有所不同。為了優化并節省AWS云服務器的費用&#xff0c;aws的合作伙伴九河云提供了一些建議&#xff1a; &#xff08;1&#xff09;調整實例大小&#xff1a;確保你使用的實例大小與你的工作負載相匹配。實例的容量每增加一倍…

Gopeed的高級用法

Gopeed是一個開源全平臺下載器&#xff0c;具體簡介請參考&#xff1a; “狗屁下載器”&#xff1f;Gopeed - 開源全平臺下載器 (免費輕量 / 比 Aria2 好用 / 遠程下載) - 異次元軟件世界 (iplaysoft.com) 這里主要介紹下自己摸索出來的 Gopeed 的高級做法。 有的網站添加的…

時政|醫療結果互認

背景&#xff08;存在的問題&#xff09; 看同一種病&#xff0c;換一家醫院甚至換一個院區、換一個科室&#xff0c;檢查檢驗還得再來一遍&#xff0c;費錢又費時。開展檢查檢驗結果互認&#xff0c;可以明顯減輕患者就醫負擔。患者不用做重復檢查&#xff0c;也可節約就醫時…

基于JSP/Servlet校園二手交易平臺(二)

目錄 2 開發技術及開發環境 2.1 Java語言簡介 2.2 J2EE技術介紹 2.3 Servlet/JSP技術 2.4 MVC 簡介 2.5 Struts 技術 2.6 Hibernate 技術 2.6.1 應用程序的分層體系結構 2.6.2 Hibernate的應用及API簡介 2.7 開發環境及環境配置 2.7.1 Java/JSP系統環境 2.7.2 JSP環…

D365 SysDictTable\SysDictField

文章目錄 前言一、示例 前言 SysDictField 和 SysDictTable 用于訪問表和字段的元數據信息。 一、示例 循環表&#xff0c;使對應數據源的字段禁止編輯 public void fieldNoAllowEdit(Common _common,formDataSource fds,boolean aE false){TableId tab…

小程序-購物車-基于SKU電商規格組件實現

SKU 概念&#xff1a; 存貨單位&#xff08; Stock Keeping Unit &#xff09;&#xff0c; 庫存 管理的最小可用單元&#xff0c;通常稱為“單品”。 SKU 常見于電商領域&#xff0c;對于前端工程師而言&#xff0c;更多關注 SKU 算法 &#xff0c;基于后端的 SKU 數據…

(二)vForm 動態表單設計器之下拉、選擇

系列文章目錄 &#xff08;一&#xff09;vForm 動態表單設計器之使用 目錄 系列文章目錄 前言 一、后端需提供接口 二、組件配置 總結 前言 動態表單下拉、選擇等組件&#xff0c;大概率要使用數據庫中的數據&#xff0c;那么vForm如何拿到數據庫中的數據呢&#xff1f;跟隨…

僵尸進程、孤兒進程、守護進程

【一】僵尸進程和孤兒進程 【1】引入 我們知道在unix/linux中&#xff0c;正常情況下&#xff0c;子進程是通過父進程創建的&#xff0c;子進程在創建新的進程。 子進程的結束和父進程的運行是一個異步過程,即父進程永遠無法預測子進程 到底什么時候結束。 當一個 進程完成它…

動物合并消除休閑游戲源碼 Animal Merge 益智游戲

一款動物合并消除休閑游戲源碼&#xff0c;Animal Merge是一款引人入勝的益智游戲&#xff0c;玩家的任務是合并方塊&#xff0c;創造出可愛的動物&#xff0c;這些動物的體型會逐漸變大。游戲玩法包括將方塊放到網格上&#xff0c;并戰略性地將它們合并以形成更大的動物形狀。…

作文筆記9 描寫方法

動態描寫&#xff1a; 威尼斯小艇&#xff0c;窗外的風景飛快的后退。 靜態描寫&#xff1a; 牧場之國&#xff0c;牛不再哞哞&#xff0c;馬忘記了踢馬房的擋板。 動靜結合&#xff1a; 火車進站&#xff0c;人聲鼎沸&#xff0c;叫賣聲&#xff0c;廣播聲&#xff0c;人…

【408精華知識】主存相關解題套路大揭秘!

講完了Cache&#xff0c;再來講講主存是怎么考察的&#xff0c;我始終認為&#xff0c;一圖勝千言&#xff0c;所以對于很多部件&#xff0c;我都是通過畫圖進行形象的記憶&#xff0c;那么接下來我們對主存也畫個圖&#xff0c;然后再來詳細解讀其考察套路~ 文章目錄 零、主存…

機器人正逆運動學、動力學概念

1.基本概念 建立機器人的正逆運動學和正逆動力學模型是為了解決不同類型的控制和規劃問題。這些模型幫助工程師和研究人員理解和預測機器人的行為&#xff0c;從而設計出更有效的控制策略和運動規劃。以下是建立這些模型的主要原因和一些應用實例&#xff1a; 正運動學模型 正…