C++ 的 out_ptr 和 inout_ptr

1 問題的起因

1.1 T**T&*

? C++ 的智能指針可以通過 get() 和 * 的重載得到原始指針 T*,遇到這樣的 C 風格的函數的時候:

void Process(Foo *ptr);std::unique_ptr<Foo> sp = ...;Process(sp.get()); //調用 Process 函數

Process() 函數以非搶奪的方式使用 Foo *,大家都相安無事。但是,C++ 的智能指針都是非侵入式的智能指針,如果要修改指針自身,則只能通過顯式的手段,比如 reset() 成員函數。所以 C++ 的智能指針遇到這樣的 C 風格的函數時,就很棘手:

bool MakeObject(Foo **pptr);
bool RefreshObject(Foo& *pptr);

沒有 const 約束,大家都明白這樣的接口是返回一個指針或重置一個指針。遇到這種情況,C++ 只能分步做這個事情:

std::unique_ptr<Foo> sp;
Foo *ptr = nullptr;
if(MakeObject(&ptr)) {sp.reset(ptr);
}

但是這么做就有個問題,在 MakeObject() 函數完成一個 Foo * 的初始化,到外部 sp 托管這個指針之間就有一段間隙,在這期間發生任何異常,Foo 對象和 Foo 對象分配的存儲空間就隨風而去,自由地飛翔。

1.2 傳統解決方案

? Raymond Chen 在資料 [2] 中給出了一種代理類的解決方法,通過代理類在智能指針和對象指針之間建立一個橋梁。就上一節的 MakeObject() 函數的使用,借助于 Raymond Chen 的思路,我們可以這樣設計一個代理類:

template<typename T>
struct UniquePtrProxy {UniquePtrProxy(std::unique_ptr<T>& output): m_output(output) { }~UniquePtrProxy() { m_output.reset(m_rawPtr); }operator T** () { return &m_rawPtr; }UniquePtrProxy(const UniquePtrProxy&) = delete;UniquePtrProxy& operator=(const UniquePtrProxy&) = delete;std::unique_ptr<T>& m_output;T *m_rawPtr = nullptr;
};

UniquePtrProxy 類通過構造函數關聯一個 T 類型的智能指針,通過對 T** 的重載,使得這個類可以適配需要 T** 參數的場合,給出的 T** 可被修改,并且在代理銷毀的時候 reset 關聯的智能指針。

std::unique_ptr<Foo> spFoo;
if (MakeObject(UniquePtrProxy<Foo>(spFoo))) {std::cout << "value = " << spFoo->GetValue() << std::endl;
}

? 貌似天衣無縫,即使 MakeObject 內部出現了異常,只要 Foo * 的指針是有效的,利用 RAII,UniquePtrProxy 都可以正確設置智能指針,從而避免資源泄露。不過,正如 Raymond Chen 在資料 [2] 中給出的例子那樣,用戶如果這樣寫代碼就很郁悶了:

if (MakeObject(UniquePtrProxy<Foo>(spFoo)) && spFoo) {std::cout << "value = " << spFoo->GetValue() << std::endl;
}

這其實是一種很合理的做法,在使用智能指針之前先檢查一下指針的有效性。但是 if 表達式中的整個求值完成之前,UniquePtrProxy 創建的臨時對象在 MakeObject() 函數調用完成后,會像鬼魂一樣繼續飄蕩一段時間,當檢測 spFoo 的時候,它還沒有析構,spFoo 還沒有被 reset,結果就是 if 代碼塊永遠也走不到。

? 資料 [4] 提出一種侵入式智能指針,允許直接修改內部指針,比如資料 [1] 的 retain_ptr 的實現。但是更多的庫采用的是智能指針適配器的方式,通過適配器完成智能指針的侵入式操作。典型的就是 WRL 庫的 ComPtrRef ,它其實是對 ComPtr 的適配器。C++ 的標準庫采用的就是適配器方案。

2 C++ 23 的智能指針適配器

2.1 out_ptr_t 和 inout_ptr_t

? C++ 23 引入了兩個智能指針適配器,即 out_ptr_t 和 inout_ptr_t,分別用于應對上一節的 MakeObject() 類型 C 函數和 RefreshObject() 類型 C 函數(有時候 RefreshObject() 類型的 C 函數也是用 T** 類型參數)。對于 MakeObject() 類型 C 函數,我們可以這樣使用 std::out_ptr_t 適配器:

int main() {std::unique_ptr<FooTest> spFoo;if (MakeObject(std::out_ptr_t<std::unique_ptr<FooTest>, FooTest*>(spFoo))) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

2.2 out_ptr 和 inout_ptr

? 正如上一節的例子,直接使用適配器需要顯式指定模板參數,非常麻煩,所以 標準庫還提供了兩個全局函數,std::out_ptr() 和 std::inout_ptr(),這兩個函數的作用就是根據函數參數推導參數類型,然后返回一個相應的適配器對象,可以簡化這兩個適配器的使用,比如上一節的例子,可以改成這樣:

int main() {std::unique_ptr<FooTest> spFoo;if (MakeObject(std::out_ptr(spFoo))) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

2.3 注意事項

? C++ 標準并不建議直接使用 std::out_ptr_t 或 std::inout_ptr_t 構建一個有聲明周期的臨時對象,因為這樣很容易導致問題,比如 2.1 節的例子,如果代碼寫成這樣:

int main() {std::unique_ptr<FooTest> spFoo;auto&& rrr = std::out_ptr_t<std::unique_ptr<FooTest>, FooTest *>(spFoo);if (MakeObject(rrr)) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

編譯器不抱怨,但是運行異常,因為 rrr 延長了 std::out_ptr_t 臨時對象的生命周期,使得它的析構在使用 spFoo 指針之后,導致 spFoo 在它消失之前一直是無效的狀態。

? 另外需要注意的是 std::inout_ptr_t 做的事情是釋放智能指針原來的所有權,然后重新初始化這個智能指針。這樣的操作需要獨占所有權的智能指針,所以它不能用于 std::shared_ptr。還有就是 1.2 節所提到的代理或適配器的生命周期問題,std::out_ptr_t 或 std::inout_ptr_t 也存在,所以這樣的代碼依然是有問題的:

int main() {std::unique_ptr<FooTest> spFoo;if (MakeObject(std::out_ptr(spFoo)) && spFoo) {std::cout << "value = " << spFoo->GetValue() << std::endl;}
}

這也是使用智能指針適配器需要注意的地方。實際上,資料 [2] 中作者提出了幾種解決方案,但是都不是很優雅的方案,大家感興趣的話可以看一下這篇文章。

3 總結

? 智能指針適配器的引入,起到了三個作用:

  • 安全封裝原生指針的傳遞

    為許多 C 函數提供安全適配器,避免手動管理指針和資源的所有權

  • 與智能指針無縫協作

    允許 C++ 的智能指針直接用于需要 T** 和 T&* 參數的函數交互,解決智能指針需要手動釋放和重置的問題,解決了此類 C 風格的 API 安全返回資源的問題

  • 統一資源管理接口

    將現有的 C 風格的資源獲取和釋放行為整合到 C++ 的 RAII 模型中,逐步替換為 RAII 風格,減少資源泄露的風險

總之,這些適配器工具是 C++ 進一步強化與 C 互操作性和資源管理的重要改進,通過對智能指針的自動適配,降低此類開發場景的資源泄漏風險,通過對智能指針的隱式管理,也使得代碼更簡潔。

參考資料

[1] retain_ptr: https://github.com/slurps-mad-rips/retain-ptr

[2] Raymond Chen: Spotting problems with destructors for C++ temporaries

[3] ComPtrRef Class: From Microsoft Windows Runtime Library

[4] P0468: A Proposal to Add an Intrusive Smart Pointer to the C++ Standard Library, 2018

[5] P1132: out_ptr - a scalable output pointer abstraction

關注作者的算法專欄
https://blog.csdn.net/orbit/category_10400723.html

關注作者的出版物《算法的樂趣(第二版)》
https://www.ituring.com.cn/book/3180

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

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

相關文章

取消 Conda 默認進入 Base 環境

在安裝 Conda 后&#xff0c;每次打開終端時默認會進入 base 環境。可以通過以下方法取消這一默認設置。 方法一&#xff1a;使用命令行修改配置 在終端中輸入以下命令&#xff0c;將 auto_activate_base 參數設置為 false&#xff1a; conda config --set auto_activate_ba…

數字計數--數位dp

1.不考慮前導零 2.每一位計數&#xff0c;就是有點“數頁碼”的意思 P2602 [ZJOI2010] 數字計數 - 洛谷 相關題目&#xff1a;記得加上前導零 數頁碼--數位dp-CSDN博客 https://blog.csdn.net/2301_80422662/article/details/148160086?spm1011.2124.3001.6209 #include…

Redis學習打卡-Day5-Redis 持久化

單點 Redis 的一些問題 數據丟失&#xff1a;Redis 是內存存儲&#xff0c;服務重啟可能會丟失數據。solution&#xff1a;實現 Redis 數據持久化。并發能力&#xff1a;單節點 Redis 并發能力雖然不錯&#xff0c;但也無法滿足如618這樣的高并發場景。solution&#xff1a;搭…

飛書知識問答深度體驗:企業AI應用落地的典范產品

飛書知識問答深度體驗&#xff1a;企業AI應用落地的典范產品 產品介紹-飛書知識問答是什么與常規通用大模型相比有何優點&#xff1f;大模型橫行的時代&#xff0c;飛書知識問答對普通人和企業有何影響呢&#xff1f; 場景示例-不同角色可以用飛書知識問答做什么&#xff1f;對…

Python打卡訓練營學習記錄Day34

知識點回歸&#xff1a; CPU性能的查看&#xff1a;看架構代際、核心數、線程數 GPU性能的查看&#xff1a;看顯存、看級別、看架構代際 GPU訓練的方法&#xff1a;數據和模型移動到GPU device上 類的call方法&#xff1a;為什么定義前向傳播時可以直接寫作self.fc1(x) CPU性…

Django的請求和響應+template模板

&#x1f31f; 如果這篇文章觸動了你的心弦&#xff0c;請不要吝嗇你的支持&#xff01; 親愛的讀者&#xff0c; 感謝你花時間閱讀這篇分享。希望這里的每一個字都能為你帶來啟發或是讓你會心一笑。如果你覺得這篇文章有價值&#xff0c;或者它解決了你一直以來的一個疑問&a…

Python |GIF 解析與構建(2):狀態機解析

Python &#xff5c;GIF 解析與構建&#xff08;2&#xff09;&#xff1a;狀態機解析 目錄 Python &#xff5c;GIF 解析與構建&#xff08;2&#xff09;&#xff1a;狀態機解析 引言 一、狀態機概述 狀態機的優勢與改進方向 總結 引言 在《Python &#xff5c;GIF 解…

PCB設計實踐(二十六)貼片電容與插件電容的全面解析:差異、演進與應用場景

一、核心差異&#xff1a;結構與性能對比 物理結構與封裝形式 貼片電容&#xff08;Surface Mount Device, SMD&#xff09;采用扁平化設計&#xff0c;外形多為長方體或圓柱體&#xff0c;直接通過焊盤固定在電路板表面。其封裝材料通常為陶瓷、聚合物或鋁電解層&#xff0c;外…

XC7A200T-2FFG1156I FPGA AMD Xilinx Artix-7

XC7A200T-2FFG1156I 是 AMD Xilinx Artix-7 系列的一款高性能低功耗 FPGA&#xff0c;采用 28 nm 高性能低功耗&#xff08;HPL&#xff09;工藝制造&#xff0c;核心電壓在 0.95 V–1.05 V 之間&#xff0c;可在 –40 C 至 100 C 工業溫度范圍內穩定工作 。 邏輯資源&#xff…

LVS + Keepalived + Nginx 高可用負載均衡系統實驗

1. 項目背景 在現代 Web 應用中&#xff0c;高可用性和負載均衡是至關重要的需求。本項目旨在通過 LVS&#xff08;Linux Virtual Server&#xff09;實現流量分發&#xff0c;通過 Keepalived 實現高可用性&#xff0c;通過 Nginx 提供后端服務。該架構能夠確保在單點故障的情…

window 顯示驅動開發-視頻內存供應和回收(一)

Windows 顯示驅動程序模型 (WDDM) 1.2 及更高版本用戶模式顯示驅動程序必須使用內存套餐和回收功能&#xff08;從Windows 8開始提供&#xff09;&#xff0c;以減少本地內存和系統內存中臨時表面所需的內存開銷。 最低 WDDM 版本&#xff1a;1.2 最低 Windows 版本&#xff…

什么是VR場景?VR與3D漫游到底有什么區別

在數字化時代&#xff0c;虛擬現實&#xff08;Virtual Reality, 簡稱VR&#xff09;場景與3D漫游作為兩種前沿技術&#xff0c;改變著人們的生活方式和體驗模式。通過計算機模擬真實或假想的場景&#xff0c;讓用戶仿佛身臨其境&#xff0c;并能與虛擬環境進行互動。盡管VR場景…

JAVA查漏補缺(2)

AJAX 什么是Ajax Ajax&#xff08;Asynchronous Javascript And XML&#xff09;&#xff0c;即是異步的JavaScript和XML&#xff0c;Ajax其實就是瀏覽器與服務器之間的一種異步通信方式 異步的JavaScript 它可以異步地向服務器發送請求&#xff0c;在等待響應的過程中&…

客服中心大模型應用演進路線:從傳統服務到超級智能助手的轉型

客服中心作為企業與客戶溝通的重要橋梁&#xff0c;近年來經歷了從人工服務到人工智能驅動的深刻變革。本文基于"客服中心大模型應用演進路線圖"&#xff0c;詳細解析客服中心從傳統模式向AI驅動智能服務的轉型歷程&#xff0c;剖析每個發展階段的特點、應用場景及關…

使用 OpenCV 實現“隨機鏡面墻”——多鏡片密鋪的哈哈鏡效果

1. 引言 “哈哈鏡”是一種典型的圖像變形效果&#xff0c;通過局部鏡面反射產生扭曲的視覺趣味。在計算機視覺和圖像處理領域&#xff0c;這類效果不僅有趣&#xff0c;還能用于藝術創作、交互裝置、視覺特效等場景。 傳統的“哈哈鏡”往往是針對整張圖像做某種鏡像或扭曲變換…

Python訓練營打卡——DAY33(2025.5.22)

目錄 簡單的神經網絡 一、PyTorch的安裝 二、準備工作 三、數據的準備 四、模型架構定義 五、模型訓練&#xff08;CPU版本&#xff09; 1. 定義損失函數和優化器 2. 開始循環訓練 3. 可視化結果 六、通俗解釋 1. 環境安裝&#xff08;相當于買鍋碗瓢盆&#xff09;…

目標檢測 Lite-DETR(2023)詳細解讀

文章目錄 迭代高級特征跨尺度融合高效的低層次特征跨尺度融合KDA&#xff1a;Key-aware Deformable Attention 論文翻譯&#xff1a; CVPR 2023 | Lite DETR&#xff1a;計算量減少60%&#xff01;高效交錯多尺度編碼器-CSDN博客 DINO團隊的 &#xff08;Lightweight Transfo…

ES(Elasticsearch) 基本概念(一)

Elasticsearch作為當前最流行的開源搜索和分析引擎&#xff0c;廣泛應用于日志分析、全文搜索、業務智能等領域。Elasticsearch是一個基于 Apache Lucene 構建的分布式搜索和分析引擎、可擴展數據存儲和矢量數據庫。它針對生產級工作負載的速度和相關性進行了優化。使用 Elasti…

當物聯網“芯”闖入納米世界:ESP32-S3驅動的原子力顯微鏡能走多遠?

上次咱們把OV2640攝像頭“盤”得明明白白&#xff0c;是不是感覺ESP32-S3這小東西潛力無限&#xff1f;今天&#xff0c;咱們玩個更刺激的&#xff0c;一個聽起來就讓人腎上腺素飆升的挑戰——嘗試用ESP32-S3這顆“智慧芯”&#xff0c;去捅一捅科學界的“馬蜂窩”&#xff0c;…

Excel合并單元格后,如何自動批量生成序號列

1.選擇整列 2.組合鍵&#xff1a;CtrlG 3.定位條件&#xff0c;選擇“空值” 4.在第一個框中輸入“MAX(”&#xff0c;鼠標選中A1框&#xff0c;后加“&#xff1a;”&#xff0c;鼠標選中前方“A1”&#xff0c;按“F4”絕對引用&#xff0c;補全右括號&#xff0c;后輸入“1…