C++進階:智能指針

目錄

  • 1. RAII與智能指針
  • 2. C++庫中的智能指針
    • 2.1 智能指針auto_ptr
    • 2.2 智能指針unique_ptr
    • 2.3 智能指針shared_ptr
  • 3. shared_ptr的循環引用
  • 4. 智能指針的定值刪除器

1. RAII與智能指針

??上一篇文章學習了異常相關的知識,其中遺留了一個異常安全相關的問題。那就是異常的拋出會打亂執行流,可能使得動態開辟的資源無法被正常釋放導致內存泄漏。而之前我們是通過異常再拋出的方式去解決這一問題的,可是,此種方式會使得代碼的可讀性極差。下面就來學習一種更好的也是現今一般會使用的解決異常安全的方式,智能指針。
??在正式學習智能指針之前,先來了解一個概念RAII(Resource Acquisition Is Initializatio)RAII是C++中的一種編程設計思路,直譯而來是資源獲得后立即初始化。而實際上是指將資源交給一個對象去幫忙管理,利用對象的生命周期來管理資源,智能指針就是RAII思想設計而得一個產物。另外的應用場景,還有,打開文件與關閉文件,打開文件的返回值一般都是指針。


智能指針的特性與功能:

  • 1. 智能指針會將資源管理起來,利用本身對象的生命周期在析構時釋放資源,防止了資源的內存泄漏
  • 2. 智能指針支持像指針一樣的操作,諸如,解引用*箭頭->
  • 3. 智能指針的拷貝不會進行深拷貝與迭代器類似,雖然其能對資源進行管理與操作,但與數據結構的存儲不一樣,數據結構中所存儲的資源是自己的,而智能指針的資源是代為持有,多個智能指針是共享一份資源的(一般為引用計數)
//智能指針管理資源的邏輯與支持指針操作
template<class  T>
class SmartPtr
{
public://管理資源SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}//像指針一樣的操作:*, ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

2. C++庫中的智能指針

2.1 智能指針auto_ptr

  • 歷史上的一個智能指針auto_ptr:

??在C++98標準時,就已經有了C++中歷史上的第一個智能指針auto_ptr。其除了具備智能指針管理資源與指針操作的功能外,對智能指針的拷貝也做了相關的實現,其設計思路為拷貝后,將指針轉移,將原有指針懸空。此種方法多有漏洞,大部分公司都禁止其的使用,可以說是C++語言歷史上的一個語法污點。可能是受此影響,C++98標準后,一些C++標準委員會庫工作組成員合理制作了一個名為boost的準標準庫,其會將一個些新的語法點進行先探索與嘗試實現,C++標準庫后續的很多語法都是從boost庫中吸收而來。

  • auto_ptr的拷貝后指針懸空與簡單實現模擬:

在這里插入圖片描述

//模擬實現
template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}//指針懸空auto_ptr(const auto_ptr<T>& p){_ptr = p._ptr;p._ptr = nullptr;}~auto_ptr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

2.2 智能指針unique_ptr

??因為C++98中auto_ptr的缺陷,boost庫中又嘗試創建實現了新的智能指針,如shared_ptr(配合new)/shared_array(配合new[])共享指針、scoped_ptr/scoped_arrary守衛指針、weak_ptr弱指針、instrusive_ptr。其中,shared_ptrscoped_ptrweak_ptr都后續被納入標準庫。后續出現的這些指針都是旨在采用不同的方式去處理智能指針拷貝的問題。boost庫中的scoped_ptr就是現在C++標準庫中的unique_ptrunique_ptr解決拷貝問題的方式是,直接禁止本身進行拷貝操作,其原理為禁止拷貝構造與賦值重載的生成,C++11前的實現方法與C++11后的實現方法不同。
??所有智能指針都包含在<memory>頭文件中,boost庫中將boost作為命名空間。
在這里插入圖片描述

//unique_ptr簡單模擬實現
template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){delete _ptr;}unique_ptr(const unique_ptr<T>& p) = delete;//拷貝構造unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;//賦值T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

2.3 智能指針shared_ptr

??與unique_ptr不同shared_ptr支持拷貝操作,其底層是以引用計數的方式來支持拷貝構造與賦值操作的實現的。但選取怎樣一個變量當作為引用計數的載體是一個值得思考的問題,智能指針的引用計數是指當前有多少個智能指針指向同一份資源。

  • 選取普通的成員變量顯然是不可行的,其無法保證拷貝時引用計數的共享性。
    在這里插入圖片描述
  • 那么,屬于整個類的靜態成員變量呢?初步考量這好像是一個可行的方案,但這個方法其實還是有漏洞,當同一類型的shared_ptr指向不同的資源時,靜態成員變量就無法解決了。
    在這里插入圖片描述
  • C++標準庫中給出的方法是,動態開辟new出一個變量,讓其存儲引用計數,這樣指向不同資源的shared_ptr就不會互相印象,當引用計數歸零時,對資源進行釋放。
    在這里插入圖片描述
    在這里插入圖片描述

shared_ptr的簡單模擬實現:

template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr = nullptr)//構造參數賦予缺省值,充當默認構造:_ptr(ptr){_pcount = new int(1);}void release(){if (--(*_pcount) == 0)//引用計數為0時,釋放資源{delete _ptr;delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& p){_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& p){//不能自己給自己賦值//if(*this != p)//指向同一份資源的智能指針賦值,特殊處理if (_ptr != p._ptr){release();_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}return *this;}int use_count(){return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const//指針指向的對象不能被改變{return _ptr;}private:T* _ptr;int* _pcount;
};

3. shared_ptr的循環引用

??shared_ptr在多種智能指針中,綜合而論已經是最優秀的智能指針了,可時它真的就完全不會造成內存泄漏的問題了嗎,我們來看下面這個場景。
在這里插入圖片描述
??自定義一個雙向鏈表的節點,鏈表的每個節點都是動態開辟而出的,這里我們采用智能指針的方式去定義與管理。但當程序執行結束時,兩個鏈表節點的資源并沒有被釋放。
在這里插入圖片描述
??當程序運行結束,node1、node2兩個shared_ptr智能指針銷毀之后。還有兩個指向節點資源的智能指針_next_prev。這就使得指向節點資源的智能指針其引用計數沒有歸0,所指向的資源也就無法釋放。被智能指針管理的資源想要被釋放,其引用計數就需要歸0,想要引用計數歸0,那么,所有指向該資源的智能指針都必須要銷毀。但在這一過程中,會出現下圖的邏輯閉環,導致節點1,節點2互相指向無法釋放的邏輯閉環,造成循環引用,內存泄漏
在這里插入圖片描述


循環引用的解決方法weak_ptr:
??為了解決上述shared_ptr的循環引用導致內存泄漏的問題,C++庫中設計了weak_ptr這樣一個智能指針,其的種種普通特性都與shared_ptr智能指針相同,但特殊的是,使用它指向shared_ptr管理的資源,shared_ptr的引用計數不增加。weak_ptr只做鏈接功能,因為weak_ptr與shared_ptr不是一個類型的智能指針,weak_ptr想要從shared_ptr獲取資源只有兩個方式,一是被聲明為友元,二是為shared_ptr添加get接口,get接口必須使用const修飾this指針。

template<class T>
class weak_ptr
{
public:weak_ptr():_ptr(nullptr){}~weak_ptr(){}weak_ptr(const shared_ptr<T>& p){_ptr = p.get();}weak_ptr<T>& operator=(const shared_ptr<T>& p){_ptr = p.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

在這里插入圖片描述
??在上面weak_ptr智能指針的模擬實現中,沒有為其添加引用計數。但在C++標準庫中weak_ptr其實也是有引用計數的。只不過它的引用計數不參與空間的釋放,weak_ptr的引用計數更像是一種監視,其的存在是為了防止weak_ptr去釋放引用計數為0已經被釋放過的空間。

4. 智能指針的定值刪除器

??上面所有關于智能指針的學習,對于shared_ptr智能指針地模擬實現,都是基于使用智能指針對單個動態開辟new出的對象做管理的情況。但當需要使用智能指針管理多個對象,或是管理非動態開辟的資源(文件指針)時,就無法去正確地釋放資源了
在這里插入圖片描述
??boost庫中對于管理多個對象的資源創造了專門與之相對應的shared_array與scoped_array。但在C++標準庫中,卻沒有采用這種方式,而是設計了一種定值刪除器的方法來控制對資源的刪除方式,使用方式如下:

//方法1:C++中特化模板,專門用于釋放new[]的資源
shared_ptr<ListNode[]> p1(new ListNode[10]);//方法2:定值刪除器,構造時傳入以仿函數對象形式傳入對應的資源釋放方法
template<class T>
struct DeleteArray
{void operator()(T* ptr){delete[] ptr;}
};//仿函數、lambda表達式、函數指針皆可
shared_ptr<ListNode> p2(new ListNode[10], DeleteArray<ListNode>());

C++標準庫中的調用接口:
在這里插入圖片描述


  • shared_ptr中定值刪除器的模擬實現
namespace zyc
{template<class T>class shared_ptr{public://定值刪除器function<void(T*)> _del = [](T* ptr) { delete ptr; };//delete普通new對象的缺省處理方法template<class D>shared_ptr(T* ptr, D del):_del(del){}shared_ptr(T* ptr = nullptr):_ptr(ptr){_pcount = new int(1);}void release(){if (--(*_pcount) == 0){_del(_ptr);//控制釋放方式delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& p){_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& p){if (_ptr != p._ptr){release();_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}return *this;}private:T* _ptr;int* _pcount;};
}

??含有定值刪除器的模板構造中,其中定值包裝器的類型是獨屬于此構造函數的模板參數類型,而不是整個類。因此,想要通過成員變量的方式讓析構函數拿到這一仿函數對象,就需要采用定義包裝器對象的方式來實現(釋放資源的仿函數其參數與返回值類型是確定的)。

  • 內存泄漏與資源泄漏:
    ??內存泄漏是指動態開辟(malloc/realloc/new)出的空間已經不再使用,可是因為疏忽(忘記free/delete)/錯誤(循環引用)的原因沒有去釋放。而資源泄漏是指申請的資源(文件描述符,管道等)在使用完成忘記釋放,資源描述符是有限的。在程序長期運行的環境下,內存泄漏可能會導致程序直接崩潰,而資源泄漏可能就會導致出現無法再打開文件等問題。
    ??一般出現上述問題,在不同環境下都有內存泄漏的檢測工具可以幫助我們發現問題,但再好的檢測工具都不如我們在編寫代碼時多加注意,提高代碼的規范性。每到必要時就去使用智能指針管理相關資源,如此就能預防避免幾乎所有的資源泄漏問題。再好的事后檢測手段,都不如事前做好預防
    ??使用cout打印char類型指針變量時,需要進行(void)強制類型轉換,因為cout會默認char*類型為打印字符串。

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

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

相關文章

Tkinter 實現按鈕鼠標懸浮提示:兩種方案(繼承Frame與不繼承)

在 Tkinter 桌面應用開發中&#xff0c;為按鈕添加“鼠標懸浮提示”是提升用戶體驗的常用功能——無需點擊&#xff0c;只需將鼠標挪到按鈕上方&#xff0c;就能自動顯示按鈕功能說明。本文將詳細介紹兩種實現方案&#xff1a;不繼承 Frame 類&#xff08;快速簡潔版&#xff0…

20250814 最小生成樹總結

引子 啊啊額&#xff0c;從一張圖里抽出幾條邊&#xff0c;組成一棵樹&#xff0c;無環n?1n-1n?1條邊&#xff0c;就是生成樹。那么邊權和最小的生成樹就叫最小生成樹&#xff0c;最大生成樹同理。 kruskal最小生成樹 要求kruskal最小生成樹&#xff0c;我們首先用結構體數組…

數據大集網:實體店獲客引流的數字化引擎,解鎖精準拓客新密碼?

?在實體店面臨流量焦慮、獲客成本攀升的當下&#xff0c;實體店獲客引流工具的重要性愈發凸顯。如何在激烈的市場競爭中精準觸達目標客戶、構建可持續的客流增長模式&#xff1f;數據大集網憑借其創新的智能獲客體系與全鏈路服務能力&#xff0c;正成為萬千實體店突破增長瓶頸…

nginx --ssl證書生成mkcert

github https://github.com/FiloSottile/mkcert/releases網盤下載地址 https://pan.baidu.com/s/1XI0879pqu7HXZMnmQ9ztaw 提取碼: 1111windows使用示例

守拙以致遠:個人IP的長青之道|創客匠人

2025年被認為是AI應用全面爆發的一年。各種人工智能工具在寫作、制圖、剪輯等領域廣泛使用&#xff0c;大大提升了個人和團隊的工作效率。對于個人IP而言&#xff0c;這類工具的出現確實帶來了新的機會&#xff0c;但也伴隨著一種現象——一些人開始過度依賴甚至神化AI&#xf…

USB 3.0 LTSSM 狀態機

USB2.0在電源供應后&#xff0c;通過Pull Up D-來決定枚舉LS&#xff0c;Pull Up D有一個USB高速握手過程&#xff0c;來決定HS FS。USB3.0則會通過鏈路訓練&#xff08;Link Training&#xff09;&#xff0c;來準備USB3.0通信。每當我們插上USB線的時候&#xff0c;對于3.0的…

MySQL窗口函數與PyMySQL以及SQL注入

MySQL窗口函數與PyMySQL實戰指南&#xff1a;從基礎到安全編程 引言 在數據處理和分析領域&#xff0c;MySQL作為最流行的關系型數據庫之一&#xff0c;其窗口函數功能為數據分析提供了強大的支持。同時&#xff0c;Python作為數據分析的主要語言&#xff0c;通過PyMySQL庫與My…

高級項目——基于FPGA的串行FIR濾波器

給大家安利一個 AI 學習神站&#xff01;在這個 AI 卷成紅海的時代&#xff0c;甭管你是硬核開發者還是代碼小白&#xff0c;啃透 AI 技能樹都是剛需。這站牛逼之處在于&#xff1a;全程用 "變量名式" 幽默 生活化類比拆解 AI&#xff0c;從入門到入土&#xff08;啊…

JPrint免費的Web靜默打印控件:PDF打印中文亂碼異常解決方案

文章目錄JPrint是什么&#xff1f;中文亂碼&#xff08;Using fallback font xxx for xxxx&#xff09;1.字體嵌入2.客戶機字體安裝開源地址相關目錄導航使用文檔端口號修改代理使用場景打印服務切換中文亂碼解決方案 JPrint是什么&#xff1f; JPrint是一個免費開源的可視化靜…

MFT 在零售行業的實踐案例與場景:加速文件集成與業務協作的高效方案

零售行業競爭激烈、數字化轉型迭代迅速&#xff0c;業務對數據與檔案的傳輸、處理和整合要求極高。無論是新品上市市場數據&#xff0c;還是供應鏈物流單據&#xff0c;集成方式不論是通過API或是檔案傳輸, 對于傳輸的穩定性,安全性與性能, 都會直接影響決策效率與顧客體驗。MF…

OSG+Qt —— 筆記1 - Qt窗口加載模型(附源碼)

?? OSG/OsgEarth 相關技術、疑難雜癥文章合集(掌握后可自封大俠 ?_?)(記得收藏,持續更新中…) OSG+Qt所用版本皆為: Vs2017+Qt5.12.4+Osg3.6.5+OsgQt(master) 效果 代碼(需將cow.osg、reflect.rgb拷貝至工程目錄下) OsgForQt.ui main.cpp

開源安全云盤存儲:Hoodik 實現端到端數據加密,Docker快速搭建

以下是對 Hoodik 的簡單介紹&#xff1a; Hoodik 是一個使用 Rust 和 Vue 開發的輕量級自托管安全云存儲解決方案采用了非對稱RSA密鑰對和AES混合加密策略&#xff0c;從文件存儲加密到數據鏈路加密&#xff0c;全程保證數據安全支持Docker一鍵私有部署&#xff0c;數據和服務…

[C++] Git 使用教程(從入門到常用操作)

1. Git 簡介 Git 是一款分布式版本控制系統&#xff0c;用來跟蹤文件變化、協作開發、管理項目版本。 它是開源的&#xff0c;由 Linus Torvalds 在 2005 年開發&#xff0c;廣泛用于開源與企業項目中。 2. 安裝 Git Windows 前往 Git 官網 下載并安裝。 安裝時建議勾選 Git…

實盤回測一體的期貨策略開發:tqsdk獲取歷史數據并回測,附python代碼

原創內容第969篇&#xff0c;專注AGI&#xff0c;AI量化投資、個人成長與財富自由。 星球好多同學希望說說實盤&#xff0c;我們就從實盤開始吧。 我們選擇tqsdk給大家講解&#xff0c;tqsdk支持免費注冊&#xff0c;使用模擬賬戶&#xff0c;歷史和實時數據&#xff0c;方便…

大模型推理框架vLLM 中的Prompt緩存實現原理

背景&#xff1a;為什么需要Prompt緩存模塊&#xff1f;在大模型問答多輪對話應用場景中&#xff0c;不同請求的 Prompt 往往有相同的前綴&#xff0c;比如&#xff1a;第一次問答&#xff1a;你是一名專業的電子產品客服&#xff0c;負責回答客戶關于手機產品的咨詢。請根據以…

Python之Django使用技巧(附視頻教程)

概述 Django 是一個高級的 Python Web 框架&#xff0c;遵循 “batteries-included”&#xff08;內置電池&#xff09;理念&#xff0c;提供了構建 Web 應用所需的大部分組件&#xff0c;讓開發者可以專注于業務邏輯而不是底層細節。視頻教程&#xff1a;https://pan.quark.cn…

sqli-labs通關筆記-第44關 POST字符型堆疊注入(單引號閉合 手工注入+腳本注入3種方法)

目錄 一、堆疊注入 二、源碼分析 1、代碼審計 2、SQL注入安全性分析 三、堆疊手注法 1、進入靶場 2、正確用戶名密碼登錄 3、堆疊注入 4、查看數據庫 四、聯合手注法 1、獲取列數 2、確認回顯位 3、獲取數據庫名 4、獲取表名 5、獲取列名 6、獲取字段 7、總結…

從深度偽造到深度信任:AI安全的三場攻防戰

前言當大模型開始“睜眼”看世界&#xff0c;偽造者也開始“閉眼”造世界。2025 WAIC釋放出的信號很明確&#xff1a;沒有AI安全底座&#xff0c;就沒有產業智能化的高樓。WAIC 把“安全”擺在與“創新”同等重要的位置&#xff0c;形成了“1 份共識框架&#xff0b;2 份重磅報…

【C++】哈希的應用:位圖和布隆過濾器

目錄 一、位圖 1.1 位圖的概念 1.2 位圖的實現 1.3 位圖的應用 二、布隆過濾器 2.1 布隆過濾器的提出 2.2 布隆過濾器的概念 2.3 布隆過濾器的插入和查找 2.4 布隆過濾器的刪除 2.5 布隆過濾器的優點 2.6 布隆過濾器的缺點 一、位圖 1.1 位圖的概念 1. 面試題 給4…

C語言:指針(4)

1. 回調函數回調函數就是指通過函數指針調用的函數。如果將函數指針作為參數傳遞給另一個函數&#xff0c;另一個函數根據指針來調這個函數&#xff0c;那么被調用的函數就是回調函數。回調函數不是由這個函數的實現方直接調用&#xff0c;而是在特定的條件下由另一方調用的。例…