【C++篇】“內存泄露”的寶藏手段:智能指針

目錄

智能指針的使用場景分析

RAII和智能指針的設計思路

C++標準庫智能指針的使用

auto_ptr的使用:

unique_ptr的使用:

shared_ptr的使用:

模擬shared_ptr:

定制刪除器:

shared_ptr的循環引用

weak_ptr


智能指針的使用場景分析

下面這段程序在“cout << Divide(len, time) << endl;“可能會拋出異常,如果不捕獲這個異常,也就是不加對應的catch語句,那么后面的delete[]就執行不到位,這樣就會造成內存泄漏。所以,我們應該加上對應的catch語句,將異常捕獲后釋放資源,再將異常重新拋出。

除了Divide會拋出異常,new的部分也會拋出異常,若是”int* array1 = new int[10];“處拋異常,倒沒什么事,因為拋出異常代表內存申請失敗,但若是”int* array2 = new int[10];“處拋異常呢?此時array1已經成功申請內存了,如果不delete掉array1的資源,就會造成內存泄漏,為了避免這樣的情況,還需在array2處在寫一個try語句。但是當存在多個變量進行new時,代碼會變的很搓,所以這里就可以使用到智能指針

double Divide(int a, int b)
{// 當b == 0時拋出異常if (b == 0){throw "Divide by zero condition!";}else{return (double)a / (double)b;}
}
void Func()
{// 這?可以看到如果發?除0錯誤拋出異常,另外下?的array和array2沒有得到釋放。// 所以這?捕獲異常后并不處理異常,異常還是交給外?處理,這?捕獲了再重新拋出去。// 但是如果array2new的時候拋異常呢,就還需要套?層捕獲釋放邏輯,這?更好解決?案// 是智能指針,否則代碼太戳了int* array1 = new int[10];int* array2 = new int[10];   // 拋異常呢try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;}catch (...){cout << "delete []" << array1 << endl;cout << "delete []" << array2 << endl;//先將異常捕獲,釋放資源,再重新拋出,這里捕獲異常的目的就是為了釋放資源,避免內存泄漏delete[] array1;delete[] array2;throw; // 異常重新拋出,捕獲到什么拋出什么}// ...cout << "delete []" << array1 << endl;delete[] array1;cout << "delete []" << array2 << endl;delete[] array2;
}
int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (const exception& e){cout << e.what() << endl;}catch (...){cout << "未知異常" << endl;}return 0;
}

RAII和智能指針的設計思路

RAII是ResourceAcquisition Is Initialization的縮寫,本質是一種利用對象?命周期來管理獲取到的動態資源。RAII在獲取資源時把資源委托給?個對象,接著控制對資源的訪問, 資源在對象的?命周期內始終保持有效,最后在對象析構的時候釋放資源,這樣保障了資源的正常 釋放,避免資源泄漏問題。

簡單講就是,申請了內存空間,但這個內存空間不需要自己管理,而是交給一個對象進行管理,當這個對象生命周期結束時,會析構,同時也會把管理資源釋放掉。
構造函數保存資源,析構函數釋放資源

#include<iostream>using namespace std;template<class T>
class SmartPtr//智能指針
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){//析構的是被智能指針管理的資源cout << "delete[]" << _ptr << endl;delete[] _ptr;}// 重載運算符,模擬指針的?為,?便訪問資源T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t i){return _ptr[i];}private:T* _ptr;
};double Divide(int a,int b)
{//如果這里拋出異常,會跳到main函數中去。根據智能指針資源在對象的?命周期內始終保持有效,最后在對象析構的時候釋放資源。所以在跳轉到main函數中去之前,會調用智能指針的析構函數將資源釋放掉if (b==0){throw "Divide:分母不能為0";}else{return ((double)a / (double)b);}}void Func()
{SmartPtr<int>sp1 = new int[10];SmartPtr<int>sp2 = new int[10];SmartPtr<int>sp3 = new int[10];SmartPtr<pair<int,int>>sp4 = new pair<int, int>[10];int len, time;cin >> len >> time;cout << Divide(len,time) << endl;sp1[5] = 50;sp4->first = 1;sp4->second = 2;cout << sp1[5] << endl;}int main()
{try{Func();}catch(const char*errmsg){cout << errmsg << endl;}catch (const exception& e){cout << e.what() << endl;}catch (...){cout << "未知異常" << endl;}return 0;
}

C++標準庫智能指針的使用

智能指針解決了拋出異常可能會導致內存泄漏的問題,但是它自身也存在一些問題。比如:智能指針的拷貝:

template<class T>
class SmartPtr
{
public:// RAIISmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << "delete[] " << _ptr << endl;delete[] _ptr;}// 重載運算符,模擬指針的?為,?便訪問資源T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t i){return _ptr[i];}private:T* _ptr;
};int main()
{//我們自己沒有實現拷貝構造,編譯器自己生成的默認拷貝構造是淺拷貝//這樣就會導致兩個智能指針指向同一塊資源,析構時就會析構兩次SmartPtr<int>sp1 = new int[10];SmartPtr<int>sp2(sp1);return 0;
}

根據前面所學,當存在申請空間時,就要調用深拷貝。但是智能指針模擬的是原生指針,原生指針1拷貝給原生指針2的目的是賦值,他們指向的資源依然是同一塊。智能指針1拷貝給智能指針2的目的是為了讓兩個智能指針共同管理這塊資源

  • C++標準庫中的智能指針都在這個頭?件下?,我們包含了就可以使用。 智能指針有好?種,除了weak_ptr他們都符合RAII和像指針?樣訪問的?為,原理上??主要是解決智能指針拷?時的思路不同的問題

  • auto_ptr是C++98時設計出來的智能指針,他的特點是拷?時把被拷?對象的資源的管理權轉移給拷貝對象,這是?個?常糟糕的設計,因為他會使被拷?對象懸空,訪問報錯的問題,也就是出現野指針的問題

  • unique_ptr是C++11設計出來的智能指針,他的名字翻譯出來是唯?指針,他的特點是不?持拷?,只?持移動。如果不需要拷貝的場景就非常建議使用他。

  • shared_ptr是C++11設計出來的智能指針,他的名字翻譯出來是共享指針,他的特點是?持拷?, 也?持移動。如果需要拷貝的場景就需要使?他了。底層是用引用計數的?式實現的。

  • weakptr是C++11設計出來的智能指針,他的名字翻譯出來是弱指針,他完全不同于上?的智能指針,他不?持RAII,也就意味著不能用它直接管理資源,weakptr的產生本質是要解決shared_ptr 的?個循環引用導致內存泄漏的問題。

auto_ptr的使用:

#include<iostream>
#include<memory>
using namespace std;struct Date
{
public:Date(int year=1,int month=1,int day=1):_year(year),_month(month),_day(day){ }~Date(){cout << "析構" << endl;}int _year;int _month;int _day;
};int main()
{auto_ptr<Date>ap1(new Date);//拷貝發生資源管理權的轉移,ap1懸空auto_ptr<Date>ap2(ap1);//空指針的訪問,這也是auto_ptr會存在的問題//ap1->_year;return 0;
}

拷貝完后,在對ap1進行訪問,這就是對空指針進行訪問:

unique_ptr的使用:

struct Date
{
public:Date(int year=1,int month=1,int day=1):_year(year),_month(month),_day(day){ }~Date(){cout << "析構" << endl;}int _year;int _month;int _day;
};int main()
{//unique_ptr只支持移動構造,不支持拷貝unique_ptr<Date>up1(new Date);//當移動完后,up1會懸空unique_ptr<Date>up2(move(up1));return 0;
}

unique_ptr與auto_ptr的區別:前者是告知使用者,自己只支持移動構造,不支持拷貝構造,使用完后會造成指針懸空的情況;后者是告知使用者自己是拷貝構造,但是會造成指針懸空的問題并未告知使用者

shared_ptr的使用:

struct Date
{
public:Date(int year=1,int month=1,int day=1):_year(year),_month(month),_day(day){ }~Date(){cout << "析構" << endl;}int _year;int _month;int _day;
};int main()
{shared_ptr<Date>sp1(new Date);shared_ptr<Date>sp2(sp1);shared_ptr<Date>sp3(sp1);//輸出引用計數cout << sp1.use_count() << endl;sp1->_year++;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;//shared_ptr還能像如下這樣賦值shared_ptr<Date>sp4=make_share<Date>(2024,1,1);return 0;
}

引用計數:當多個對象管理同一塊資源時,用一個count記錄對象個數,當count==0時,再將資源釋放

模擬shared_ptr:

namespace liu
{template<class T>class shared_ptr{public:shared_ptr(T*ptr=nullptr):_ptr(ptr),_pcount(new int(1)){ }~shared_ptr(){if (--(*_pcount) == 0){cout << "析構" << endl;delete _pcount;delete _ptr;}}shared_ptr(const shared_ptr<T>&sp):_ptr(sp._ptr),_pcount(sp._pcount){(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>&sp){if (_ptr==sp._ptr){return *this;}if (--(*_pcount)==0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;};
}

如果以int count的方式來計數:

如果以靜態成員變量的方式來計數:

定制刪除器:

智能指針析構時默認是進?delete釋放資源,這也就意味著如果不是new出來的資源,交給智能指 針管理,析構時就會崩潰。

//原生指針的空間、malloc來的空間、new[]出來的空間或者其他不是new出來的空間,都不能交給智能指針管理
shared_ptr<Date>sp1(new Date[10]);

面對這種情況,有兩種解決方案:

第一種:

//模板特化
shared_ptr<Date[]>sp2(new Date[10]);

第二種:

//定制刪除器struct Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}~Date(){cout << "析構" << endl;}int _year;int _month;int _day;
};class Fclose
{
public:void operator()(FILE*ptr){cout << "flcose()" << endl;fclose(ptr);}};template<class T>
void DeleteArrayFunc(T *ptr)
{cout << "函數指針" << endl;delete[] ptr;
}int main()
{//傳仿函數shared_ptr<FILE>sp3(fopen("智能指針.cpp","r"),Fclose());//傳lambdashared_ptr<FILE>sp4(fopen("智能指針.cpp", "r"), [](FILE* ptr) {cout << "fclose()" << endl;shared_ptr<Date>sp5(new Date[10], [](Date* ptr) {cout << "delete[]" << endl;delete[] ptr; })shared_ptr<Date>sp6(new Date[10], DeleteArrayFunc<Date>);fclose(ptr); });return 0;
}

unique_ptr定制刪除器智是在模板處,shared_ptr定制刪除器是在構造函數參數處

uniqueptr傳定制刪除器如果不想傳仿函數想以其他形式傳如刪除器的話,會很麻煩。 sharedptr定制刪除器的位置是在函數參數的位置,編譯器會自動推導類型。

//unique_ptr不以傳仿函數的方式傳入定制刪除器
//lambda
auto fcloseFunc = [](FILE* ptr) {fclose(ptr); };
unique_ptr<FILE, decltype(fcloseFunc)>up1(fopen("智能指針.cpp", "r"), fcloseFunc);
//函數指針
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);

shared_ptr的循環引用

shared_ptr?多數情況下管理資源非常合適,?持RAII,也?持拷貝。但是在循環引用的場景下會 導致資源沒得到釋放造成內存泄漏,

  • 如下圖所述場景:這樣循環引用的問題就會造成內存泄漏

  • 把ListNode結構體中的_next和_prev改成weak_ptr,weak_ptr綁定到shared_ptr時不會增加它的引用計數,_next和_prev不參與資源釋放管理邏輯,就成功打破了循環引用,解決了這?的問題

weak_ptr

weakptr不?持RAII,也不?持訪問資源,所以我們看?檔發現weakptr構造時不?持綁定到資 源,只?持綁定到sharedptr,綁定到sharedptr時,不增加shared_ptr的引用計數,那么就可以 解決上述的循環引?問題。

  • weakptr也沒有重載operator*和operator->等,因為他不參與資源管理,那么如果他綁定的 sharedptr已經釋放了資源,那么他去訪問資源就是很危險的。

  • weakptr?持expired檢查指向的 資源是否過期,usecount也可獲取sharedptr的引?數,weakptr想訪問資源時,可以調用lock返回?個管理資源的sharedptr,如果資源已經被釋放,返回的sharedptr是?個空對象,如 果資源沒有釋放,則通過返回的shared_ptr訪問資源是安全的。

sharedptr中的count計數在sharedptr釋放時不會立即釋放,因為它還需要提供給weakptr使用,如果立即釋放了,就會造成weakptr野指針的情況。

weak_ptr中還有expired接口來檢查資源是否過期。

	shared_ptr<string>sp1(new string("11111"));shared_ptr<string>sp2(sp1);weak_ptr<string>wp1 = sp1;cout << wp1.expired() << endl;cout << wp1.use_count() << endl;

如果shareptr的資源是weakptr所需要的,那么可以使用lock()接口在資源釋放前將鎖住鎖住。

鎖住資源實際上就是再用一個shared_ptr指針來管理該資源。

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

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

相關文章

【密碼學】4. 分組密碼

目錄分組密碼分組密碼概述Feistel 密碼結構數據加密標準&#xff08;DES&#xff09;差分密碼分析與線性密碼分析分組密碼的運行模式國際數據加密算法&#xff08;IDEA&#xff09;高級加密標準&#xff08;AES&#xff0c;Rijndael&#xff09;中國商用密碼 SM4祖沖之密碼&…

單片機(STM32-WIFI模塊)

一、WIFI模塊介紹 1. ESP12-F模組介紹 1.1 簡介 ESP12-F模組&#xff08;安信可&#xff08;Ai-Thinker&#xff09;ESP8266系列模組&#xff09;是一款基于樂鑫&#xff08;Espressif&#xff09;公司ESP8266芯片的Wi-Fi無線通信模塊&#xff0c;廣泛應用于物聯網&#xff0…

PyTorch 數據類型和使用

關于PyTorch的數據類型和使用的學習筆記 系統介紹了PyTorch的核心數據類型Tensor及其應用。Tensor作為多維矩陣數據容器&#xff0c;支持0-4維數據結構&#xff08;標量到批量圖像&#xff09;&#xff0c;并提供了多種數值類型&#xff08;float32/int64等&#xff09;。通過…

[python刷題模板] LogTrick

[python刷題模板] LogTrick 一、 算法&數據結構1. 描述2. 復雜度分析3. 常見應用4. 常用優化二、 模板代碼1. 特定或值的最短子數組2. 找特定值3. 找位置j的最后一次被誰更新4. 問某個或和的數量三、其他四、更多例題五、參考鏈接一、 算法&數據結構 1. 描述 LogTric…

Vim與VS Code

Vim is a clone, with additions, of Bill Joys vi text editor program for Unix. It was written by Bram Moolenaar based on source for a port of the Stevie editor to the Amiga and first released publicly in 1991.其實這個本身不是 IDE &#xff08;只有在加入和配置…

[2025CVPR-圖象分類方向]CATANet:用于輕量級圖像超分辨率的高效內容感知標記聚合

?1. 研究背景與動機? ?問題?&#xff1a;Transformer在圖像超分辨率&#xff08;SR&#xff09;中計算復雜度隨空間分辨率呈二次增長&#xff0c;現有方法&#xff08;如局部窗口、軸向條紋&#xff09;因內容無關性無法有效捕獲長距離依賴。?現有局限?&#xff1a; SPI…

課題學習筆記3——SBERT

1 引言在構建基于知識庫的問答系統時&#xff0c;"語義匹配" 是核心難題 —— 如何讓系統準確識別 "表述不同但含義相同" 的問題&#xff1f;比如用戶問 "對親人的期待是不是欲&#xff1f;"&#xff0c;系統能匹配到知識庫中 "追名逐利是欲…

在Word和WPS文字中把全角數字全部改為半角

大部分情況下我們在Word或WPS文字中使用的數字或標點符號都是半角&#xff0c;但是有時不小心按錯了快捷鍵或者點到了輸入法的全角半角切換圖標&#xff0c;就輸入了全角符號和數字。不用擔心&#xff0c;使用它們自帶的全角、半角轉換功能即可快速全部轉換回來。一、為什么會輸…

數據結構的基本知識

一、集合框架1、什么是集合框架Java集合框架(Java Collection Framework),又被稱為容器(container),是定義在java.util包下的一組接口(interfaces)和其實現類(classes).主要表現為把多個元素(element)放在一個單元中,用于對這些元素進行快速、便捷的存儲&#xff08;store&…

WebStack-Hugo | 一個靜態響應式導航主題

WebStack-Hugo | 一個靜態響應式導航主題 #10 shenweiyan announced in 1.3-折騰 WebStack-Hugo | 一個靜態響應式導航主題#10 ?編輯shenweiyan on Oct 23, 2023 6 comments 7 replies Return to top shenweiyan on Oct 23, 2023 Maintainer Via&#xff1a;我給自己…

01 基于sklearn的機械學習-機械學習的分類、sklearn的安裝、sklearn數據集、數據集的劃分、特征工程中特征提取與無量綱化

文章目錄機械學習機械學習分類1. 監督學習2. 半監督學習3. 無監督學習4. 強化學習機械學習的項目開發步驟scikit-learn1 scikit-learn安裝2 sklearn數據集1. sklearn 玩具數據集鳶尾花數據集糖尿病數據集葡萄酒數據集2. sklearn現實世界數據集20 新聞組數據集3. 數據集的劃分特…

攜全雙工語音通話大模型亮相WAIC,Soul重塑人機互動新范式

近日&#xff0c;WAIC 2025在上海隆重開幕。作為全球人工智能領域的頂級盛會&#xff0c;本屆WAIC展覽聚焦底層能力的演進與具體垂類場景的融合落地。堅持“模應一體”方向、立足“AI社交”的具體場景&#xff0c;Soul App此次攜最新升級的自研端到端全雙工語音通話大模型亮相&…

第2章 cmd命令基礎:常用基礎命令(1)

Hi~ 我是李小咖&#xff0c;主要從事網絡安全技術開發和研究。 本文取自《李小咖網安技術庫》&#xff0c;歡迎一起交流學習&#x1fae1;&#xff1a;https://imbyter.com 本節介紹的命令有目錄操作&#xff08;cd&#xff09;、清屏操作&#xff08;cls&#xff09;、設置顏色…

Java 10 新特性解析

Java 10 新特性解析 文章目錄Java 10 新特性解析1. 引言2. 本地變量類型推斷&#xff08;JEP 286&#xff09;2.1. 概述2.2. 使用場景2.3. 限制2.4. 與之前版本的對比2.5. 風格指南2.6. 示例代碼2.7. 優點與注意事項3. 應用程序類數據共享&#xff08;JEP 310&#xff09;3.1. …

【WRF工具】服務器中安裝編譯GrADS

目錄 安裝編譯 GrADS 所需的依賴庫 conda下載庫包 安裝編譯 GrADS 編譯前檢查依賴可用性 安裝編譯 GrADS 參考 安裝編譯 GrADS 所需的依賴庫 以統一方式在 $HOME/WRFDA_LIBS/grads_deps 下安裝所有依賴: # 選擇一個目錄用于安裝所有依賴庫 export DIR=$HOME/WRFDA_LIBS庫包1…

數據結構之隊列(C語言)

1.隊列的定義&#xff1a; 隊列&#xff08;Queue&#xff09;是一種基礎且重要的線性數據結構&#xff0c;遵循先進先出&#xff08;FIFO&#xff09;?? 原則&#xff0c;即最早入隊的元素最先出隊&#xff0c;與棧不同的是出隊列的順序是固定的。隊列具有以下特點&#xff…

C#開發基礎之深入理解“集合遍歷時不可修改”的異常背后的設計

前言 歡迎關注【dotnet研習社】&#xff0c;今天我們聊聊一個基礎問題“集合已修改&#xff1a;可能無法執行枚舉操作”背后的設計。 在日常 C# 開發中&#xff0c;我們常常會操作集合&#xff08;如 List<T>、Dictionary<K,V> 等&#xff09;。一個新手開發者極…

【工具】圖床完全指南:從選擇到搭建的全方位解決方案

前言 在數字化內容創作的時代&#xff0c;圖片已經成為博客、文檔、社交媒體等平臺不可或缺的元素。然而&#xff0c;如何高效、穩定地存儲和分發圖片資源&#xff0c;一直是內容創作者面臨的重要問題。圖床&#xff08;Image Hosting&#xff09;作為專門的圖片存儲和分發服務…

深度學習篇---PaddleDetection模型選擇

PaddleDetection 是百度飛槳推出的目標檢測開發套件&#xff0c;提供了豐富的模型庫和工具鏈&#xff0c;覆蓋從輕量級移動端到高性能服務器的全場景需求。以下是核心模型分類、適用場景及大小選擇建議&#xff08;通俗易懂版&#xff09;&#xff1a;一、主流模型分類及適用場…

cmseasy靶機密碼爆破通關教程

靶場安裝1.首先我們需要下載一個cms靶場CmsEasy_7.6.3.2_UTF-8_20200422,下載后解壓在phpstudy_pro的網站根目錄下。2.然后我們去訪問一下安裝好的網站&#xff0c;然后注冊和鏈接數據庫3.不知道自己數據庫密碼的可以去小皮面板里面查看4.安裝好后就可以了來到后臺就可以了。練…