c++11 :智能指針

目錄

一?為什么需要智能指針?

二?智能指針的使用及原理?

1. RAII

2. auto_ptr

3. unique_ptr

4. shared_ptr

5.?weak_ptr

三 內存泄漏

1.什么是內存泄漏,內存泄漏的危害

2.?如何避免內存泄漏?


一?為什么需要智能指針?

🚀為什么需要智能指針? 下面我們先分析一下下面這段程序有沒有什么內存方面的問題?

#include <iostream>
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0)//拋異常throw invalid_argument("除0錯誤");return a / b;
}
void f1()
{int* p = new int;cout << div() << endl;delete p;
}
int main()
{//捕異常try{f1();}catch (exception& e){cout << e.what() << endl;}return 0;
}

運行結果

通過上面的程序中我們可以看到,new了以后,而且也delete了,但是因為拋異常有點早,程序執行不到delete的位置,所以就導致內存泄露了,此外如果我們在寫代碼的過程中忘了釋放資源的話也會導致內存泄漏。為了解決上述問題,接下來引入智能指針。

🍉:我們首先寫一個類

#pragma oncetemplate<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){if (_ptr){std::cout << "delete" << _ptr<<std::endl;delete _ptr;}}
private:T* _ptr;
};
#include <iostream>
#include "SmartPtr.h"
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0)//拋異常throw invalid_argument("除0錯誤");return a / b;
}
void f1()
{/// 修改部分,將指針存儲在SmartPtr這個類中int* p = new int;SmartPtr<int> sp(p);cout << div() << endl;//delete p;
}
int main()
{//捕異常try{f1();}catch (exception& e){cout << e.what() << endl;}return 0;
}

測試結果:?

上面我們通過創建一個類SmartPtr ,讓?SmartPtr sp(p)對p進行管理資源的釋放。無論函數正常結束,還是拋異常,都會導致sp對象的生命周期到了以后,調用析構函數~SmartPtr()釋放內存。

?上述我們通過類對資源p進行管理,幫我們管理資源的釋放,這個類我們就叫智能指針


二?智能指針的使用及原理?

1. RAII

RAII(Resource Acquisition Is Initialization)是一種利用對象生命周期來控制程序資源(如內存、互斥量等)的簡單技術。

在對象構造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內始終保持有效,最后在對象析構的 時候釋放資源。借此,我們實際上把管理一份資源的責任托管給了一個對象。這種做法有兩大好處:

  • 不需要顯式地釋放資源。
  • 采用這種方式,對象所需的資源在其生命期內始終保持有效。

RAII和智能指針的關系:RAII是一種托管資源的思想,智能指針就是依靠這種RAII實現的。


觀察上述代碼

void f1()
{/// 修改部分,將指針存儲在SmartPtr這個類中int* p = new int;SmartPtr<int> sp(p);cout << div() << endl;//delete p;
}

我們還可以直接這樣

SmartPtr<int> sp(new int);/修改后的cout << div() << endl;

?但是這樣的話我們又會面臨一個問題那就是 如果我們想要訪問這個指針變量,我們需要再加以下成員函數。

	T& operator*(){return *_ptr;}T* operator->(){return _ptr;}

這樣的話我們就可以訪問和修改指針變量了

    SmartPtr<int> sp1(new int);*sp1 = 10;SmartPtr<pair<int, int>> sp2(new pair<int, int>);sp2->first = 20;sp2->second = 30;

?并且會自動釋放內存

?以上是智能指針的簡單demo,但是上述代碼還存在很多問題,我們通過以下代碼進行測試

int main()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2 = sp1;//拷貝構造sp2(sp1);return 0;
}

測試結果:

🍎問題分析:我們并沒有給SmartPtr構造拷貝構造函數,編譯器會自動生成默認拷貝構造即值拷貝(淺拷貝),sp1---------->資源, sp2----------->資源 ,當sp1和sp2出了作用域會對資源進行析構,即sp2先對資源進行析構,sp1又對同一份資源進行了析構,同一份資源不能析構二次,因為第一次析構以及釋放了所以出現了報錯。

?上述原因就在于淺拷貝造成了報錯,但是我們不能說即然淺拷貝有問題,我們進行深拷貝不就行了嗎?答案是不可以的,智能指針是用來模擬原生指針的 ,原生指針 p1=p2;就是值拷貝代表著指向同一塊空間。

接下來我們引出解決上述問題的三種解決方法:

  1. 管理器轉移:c++98 auto_ptr?
  2. 防拷貝:? ? ? ?c++11 unique_ptr?
  3. 引言計數? ? ?c++11 shared_ptr? (循環引言的問題,又需要weak_ptr來解決)

2. auto_ptr

我們對auto_ptr的拷貝函數進行構造

//拷貝構造auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}

測試如下:

int main()
{lt::auto_ptr<int> sp1(new int);lt::auto_ptr<int> sp2 = sp1;//拷貝構造sp2(sp1);return 0;
}

為什么只析構一次,并且為什么auto_ptr叫做管理權轉移呢??我們通過下圖進行描述

?

?管理權轉移:早期c++98設計缺陷,因為它會把其他指針置為空,不建議使用。


3. unique_ptr

unique_ptr的實現原理:簡單粗暴的防拷貝

		unique_ptr(unique_ptr<T>& up) = delete;unique_ptr<T>& operator==(unique_ptr<T>& up) = delete;

但是unique也有缺陷,如果有需要拷貝的場景,就無法使用。?所以c++11又搞出一個智能shared_ptr.


4. shared_ptr

shared_ptr :是通過引用計數的方式來實現多個shared_ptr對象之間共享資源

  • shared_ptr在其內部,給每個資源都維護了著一份計數,用來記錄該份資源被幾個對象共享
  • 在對象被銷毀時(也就是析構函數調用),就說明自己不使用該資源了,對象的引用計數減一。 3. 如果引用計數是0,就說明自己是最后一個使用該資源的對象,必須釋放該資源; 4. 如果不是0,就說明除了自己還有其他對象在使用該份資源,不能釋放該資源,否則其他對象就成野指 針了。?
#pragma oncenamespace lt
{template<class T>class shared_ptr{public:shared_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)){}shared_ptr(shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (this != &sp){if (--*(_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);		}}T& operator*()//const 這里加是為了const this 指針。不能說(const){return *_ptr;}T* operator->(){return _ptr;}~shared_ptr(){if (--(*_pcount)==0 && _ptr){std::cout << "delete" << _ptr << std::endl;delete _ptr;_ptr = nullptr;delete _pcount;_pcount = nullptr;}}private:T* _ptr;int* _pcount;};
}

接下來我們側重講一下拷貝構造?

這里有個注意的點就是當計數為1進行拷貝是需要注意 delete _ptr delete _pcount


5.?weak_ptr

shared_ptr?多數情況下管理資源?常合適,?持RAII,也?持拷?。但是在循環引?的場景下會導致資源沒得到釋放內存泄漏,所以我們要認識循環引?的場景和資源沒釋放的原因,并且學會使?weak_ptr解決這種問題。

struct listNode
{int _data;shared_ptr<listNode> _next;shared_ptr<listNode> _prev;};

?如圖所示:

?

  • ? 右邊的節點什么時候釋放呢,左邊節點中的_next管著呢,_next析構后,右邊的節點就釋放了。
  • next什么時候析構呢,_next是左邊節點的的成員,左邊節點釋放,_next就析構了。
  • 左邊節點什么時候釋放呢,左邊節點由右邊節點中的_prev管著呢,_prev析構后,左邊的節點就釋放了。
  • _prev什么時候析構呢,_prev是右邊節點的成員,右邊節點釋放,_prev就析構了。

此邏輯上成功形成回旋鏢似的循環引?,誰都不會釋放就形成了循環引?,導致內存泄漏。

?解決方法:把ListNode結構體中的_next和_prev改成weak_ptr,weak_ptr 不增加它的引?計數,就成功打破了循環引?,這就解決了這?的問題。

🍏weak_ptr 的簡單實現?

template<class T>class weak_ptr{public:weak_ptr() = default;weak_ptr(shared_ptr<T>& sp):_ptr(sp.get_ptr()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get_ptr();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}	private:T* _ptr;};
}

weak_ptr嚴格來說不是智能指針,因為它沒有RAII資源管理,weak_ptr是用來專門解決shared_ptr循環引用造成的問題。我們知道ListNode這個結點本身放在智能指針shared_ptr是沒有什么問題的,可以很好的對資源ListNode*進行管理,但是就是因為ListNode 結點中還包含 _next ,_prev這就造成了循環引用。我們希望的是ListNode內的指針不參與計數,

所以我們創建了weak_ptr(shared_ptr<T>& sp)?,目的就是希望把_next,_prev指針存儲再weak_ptr中不參與計數。

struct ListNode
{int val;lt::weak_ptr<ListNode> _spnext;lt::weak_ptr<ListNode> _spprev;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{lt::shared_ptr<ListNode> spn1(new ListNode);lt::shared_ptr<ListNode> spn2(new ListNode);spn1->_spnext = spn2;//不想參與計數所以我們希望把spn2(shared_ptr<ListNode>)賦值給spn1->_spnext(weak_ptr<ListNode>)//這種操作不計數//所以我們構造了 _spnext為lt::weak_ptr<ListNode>類型,并且構造了weak_ptr<T>& operator=(const shared_ptr<T>& sp)//僅僅把指針拷貝不計數。spn2->_spprev = spn1;return 0;
}

三 內存泄漏

1.什么是內存泄漏,內存泄漏的危害

  • ?什么是內存泄漏:內存泄漏指因為疏忽或錯誤造成程序未能釋放已經不再使?的內存,?般是忘記釋放或者發?異常釋放程序未能執?導致的。內存泄漏并不是指內存在物理上的消失,?是應?程序分配某段內存后,因為設計錯誤,失去了對該段內存的控制,因?造成了內存的浪費
  • .內存泄漏的危害:普通程序運??會就結束了出現內存泄漏問題也不?,進程正常結束,?表的映射關系解除,物理內存也可以釋放。?期運?的程序出現內存泄漏,影響很?,如操作系統、后臺服務、?時間運?的客?端等等,不斷出現內存泄漏會導致可?內存不斷變少,各種功能響應越來越慢,最終卡死

2.?如何避免內存泄漏?

  • 盡量使?智能指針來管理資源?
  • 定期使?內存泄漏?具檢測

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

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

相關文章

大模型在直腸癌預測及治療方案制定中的應用研究

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的 1.3 研究方法與創新點 二、大模型技術概述 2.1 大模型的基本原理 2.2 常見大模型類型及特點 2.3 在醫療領域的應用進展 三、直腸癌預測相關數據收集與處理 3.1 數據來源 3.2 數據清洗與預處理 3.3 特征工程 四、大…

VRRP與防火墻雙機熱備實驗

目錄 實驗一&#xff1a;VRRP負載均衡與故障切換 實驗拓撲?編輯一、實驗配置步驟 1. 基礎網絡配置 2. VRRP雙組配置 二、關鍵驗證命令 1. 查看VRRP狀態 2. 路由表驗證 三、流量分析 正常負載均衡場景&#xff1a; 故障切換驗證&#xff1a; 實驗二&#xff1a;防火…

OpenCV中的SIFT特征提取

文章目錄 引言一、SIFT算法概述二、OpenCV中的SIFT實現2.1 基本使用2.1.1 導入庫2.1.2 圖片預處理2.1.3 創建SIFT檢測器2.1.4 檢測關鍵點并計算描述符2.1.5 檢測關鍵點并計算描述符并對關鍵點可視化2.1.6 印關鍵點和描述符的形狀信息 2.2 參數調優 三、SIFT的優缺點分析3.1 優點…

【信息系統項目管理師】高分論文:論成本管理與采購管理(信用管理系統)

更多內容請見: 備考信息系統項目管理師-專欄介紹和目錄 文章目錄 論文1、規劃成本管理2、成本估算3、成本預算4、成本控制論文 2019年1月,我作為項目經理參與了 XX基金管理有限公司信用管理系統項目。該項目成 本1000萬,建設期為1年。通過該項目,XX基金管理有限公司在信用…

從邊緣到云端,如何通過時序數據庫 TDengine 實現數據的全局洞

在當今數字化轉型加速的背景下&#xff0c;海量的數據生成和實時處理需求已成為企業面臨的關鍵挑戰。無論是物聯網設備、工業自動化系統&#xff0c;還是智能城市的各類傳感器&#xff0c;數據的采集、傳輸與分析效率&#xff0c;直接影響企業的決策與運營。為此&#xff0c;TD…

Axure全局變量的含義與基礎應用

親愛的小伙伴,在您瀏覽之前,煩請關注一下,在此深表感謝! Axure產品經理精品視頻課已登錄CSDN可點擊學習https://edu.csdn.net/course/detail/40420 課程主題:全局變量 主要內容:全局變量含義、基礎應用 應用場景:元件賦值 案例展示: 案例視頻:

題目 3320: 藍橋杯2025年第十六屆省賽真題-產值調整

題目 3320: 藍橋杯2025年第十六屆省賽真題-產值調整 時間限制: 2s 內存限制: 192MB 提交: 549 解決: 122 題目描述 偏遠的小鎮上&#xff0c;三兄弟共同經營著一家小型礦業公司 “兄弟礦業”。公司旗下有三座礦山&#xff1a;金礦、銀礦和銅礦&#xff0c;它們的初始產值分別用…

常見緩存淘汰算法(LRU、LFU、FIFO)的區別與實現

一、前言 緩存淘汰算法主要用于在內存資源有限的情況下&#xff0c;優化緩存空間的使用效率。以確保緩存系統在容量不足時能夠智能地選擇需要移除的數據。 二、LRU&#xff08;Least Recently Used&#xff09; 核心思想&#xff1a;淘汰最久未被訪問的數據。實現方式&#x…

linux ptrace 圖文詳解(七) gdb、strace跟蹤系統調用

目錄 一、gdb/strace 跟蹤程序系統調用 二、實現原理 三、代碼實現 四、總結 &#xff08;代碼&#xff1a;linux 6.3.1&#xff0c;架構&#xff1a;arm64&#xff09; One look is worth a thousand words. —— Tess Flanders 相關鏈接&#xff1a; linux ptrace 圖…

Git基本使用(很詳細)

一&#xff1a;Git 概述 1.1 定義&#xff1a;分布式版本控制系統 1.2 版本控制 &#xff08;1&#xff09;定義&#xff1a; 版本控制時一種記錄文件內容變化&#xff0c;以便將來查閱特定版本修訂情況的系統 &#xff08;2&#xff09;舉例 多副本 優化&#xff1a; 不使用多…

23種設計模式-結構型模式之橋接模式(Java版本)

Java 橋接模式&#xff08;Bridge Pattern&#xff09;詳解 &#x1f309; 什么是橋接模式&#xff1f; 橋接模式用于將抽象部分與實現部分分離&#xff0c;使它們可以獨立變化。 通過在兩個獨立變化的維度之間建立“橋”&#xff0c;避免因多維度擴展導致的類爆炸。 &#x…

基于SIMMECHANICS的單自由度磁懸浮隔振器PID控制系統simulink建模與仿真

目錄 1.課題概述 2.系統仿真結果 3.核心程序與模型 4.系統原理簡介 4.1 單自由度磁懸浮減振器工作原理簡介 4.2 SIMMECHANICS工具箱 5.完整工程文件 1.課題概述 基于SIMMECHANICS的單自由度磁懸浮隔振器PID控制系統simulink建模與仿真。其中&#xff0c;SIMMECHANICS是M…

contenthash 持久化緩存

以下是關于持久化緩存(contenthash)的深度技術解析,涵蓋原理、配置策略及最佳實踐,幫助我們構建高性能前端應用的緩存體系: 一、緩存機制核心原理 1. 瀏覽器緩存決策矩陣 觸發條件緩存行為對應場景URL 未變化 + 強緩存有效直接讀取磁盤/內存緩存未修改的靜態資源URL 變化…

【前端記事】關于electron的入門使用

electron入門使用 背景how to start第一步 創建一個vite-vue3項目第二步 裝各種依賴第三步 配置vite.config.jspackage.jsonelectron入口 啟動重寫關閉、隱藏、最大化最小化 背景 最近對electron比較感興趣&#xff0c;折騰一段時間后有了點眉目&#xff0c;記錄一下 how to …

跨瀏覽器音頻錄制:實現兼容的音頻捕獲與WAV格式生成

在現代Web開發中&#xff0c;音頻錄制功能越來越受到開發者的關注。無論是在線會議、語音識別還是簡單的語音留言&#xff0c;音頻錄制都是一個重要的功能。然而&#xff0c;實現一個跨瀏覽器的音頻錄制功能并非易事&#xff0c;因為不同瀏覽器對音頻錄制API的支持存在差異。本…

Semantic Kernel也能充當MCP Client

背景 筆者之前&#xff0c;分別寫過兩篇關于Semantic Kernel&#xff08;下簡稱SK&#xff09;相關的博客&#xff0c;最近模型上下文協議&#xff08;下稱MCP&#xff09;大火&#xff0c;實際上了解過SK的小伙伴&#xff0c;一看到 MCP的一些具體呈現&#xff0c;會發現&…

識別圖片內容OCR并重命名文件

在工作場景中&#xff0c;經常出現通過拍攝設備獲取圖片后&#xff0c;未及時進行有效命名的情況。這些圖片中往往包含關鍵信息&#xff08;如合同編號、產品型號、日期等&#xff09;&#xff0c;需要人工識別并命名&#xff0c;存在以下痛點&#xff1a; 效率低下&#xff1…

【防火墻 pfsense】3 portal

&#xff08;1&#xff09;應該考慮的問題&#xff1a; ->HTTPS 連接的干擾問題&#xff1a;HTTPS 是一種旨在防止惡意第三方截取和篡改流量的協議。但強制門戶的工作原理是截取并改變終端用戶與網絡之間的連接。這對于 HTTP 流量來說不是問題&#xff0c;但使用 HTTPS 加密…

銀發科技:AI健康小屋如何破解老齡化困局

隨著全球人口老齡化程度的不斷加深&#xff0c;如何保障老年人的健康、提升他們的生活質量&#xff0c;成為了社會各界關注的焦點。 在這場應對老齡化挑戰的戰役中&#xff0c;智紳科技順勢而生&#xff0c;七彩喜智慧養老系統構筑居家養老安全網。 而AI健康小屋作為一項創新…

TCP協議理解

文章目錄 TCP協議理解理論基礎TCP首部結構圖示字段逐項解析 TCP是面向連接&#xff08;Connection-Oriented&#xff09;面向連接的核心表現TCP 面向連接的核心特性TCP 與UDP對比 TCP是一個可靠的(reliable)序號與確認機制&#xff08;Sequencing & Acknowledgment&#xf…