【C++】深入解析C++智能指針:從auto_ptr到unique_ptr與shared_ptr

文章目錄

  • 前言:
  • 1. 智能指針的使用及原理
  • 2. C++ 98 標準庫中的 auto_ptr:
  • 3. C++ 11 中的智能指針
    • 循環引用:
    • shared_ptr 定制刪除器
  • 4. 內存泄漏
  • 總結:

前言:

隨著C++語言的發展,智能指針作為現代C++編程中管理動態分配內存的一種重要工具,越來越受到開發者的青睞。智能指針不僅簡化了內存管理,還有助于避免內存泄漏等常見問題。本文將深入探討智能指針的使用及其原理,從C++98標準庫中的auto_ptr開始,逐步過渡到C++11中更為強大和靈活的智能指針類型,如unique_ptrshared_ptr。此外,文章還將討論循環引用問題、內存泄漏的原因及其危害,并提供相應的解決方案。通過本文的學習,讀者將能夠更好地理解和運用智能指針,編寫出更安全、更高效的C++代碼。

1. 智能指針的使用及原理

RAII(Resource Acquisition Is Initialization)是一種利用對象生命周期來控制程序資源(如內
存、文件句柄、網絡連接、互斥量等等)的簡單技術。
在對象構造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內始終保持有效,最后在
對象析構的時候釋放資源
。借此,我們實際上把管理一份資源的責任托管給了一個對象。這種做
法有兩大好處:

  • 不需要顯式地釋放資源。
  • 采用這種方式,對象所需的資源在其生命期內始終保持有效。
// SmartPtr.h
// 使用RAII思想設計的smartPtr類template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr) {std::cout << "delete: " << _ptr << std::endl;delete _ptr;}}private:T* _ptr;
};int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0錯誤");return a / b;
}
void Func()
{ShardPtr<int> sp1(new int);ShardPtr<int> sp2(new int);cout << div() << endl;
}int main()
{try {Func();}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}
//test.cpp
#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 Func()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(new int);cout << div() << endl; 
}int main()
{try {Func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

在這里插入圖片描述

  • 需要像指針一樣的去使用:
// 像指針一樣使用
T& operator*()
{return *_ptr;
}T* operator->()
{return _ptr;
}
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(new int(0));
*sp1 += 10;SmartPtr<pair<string, int>> sp3(new pair<string, int>);
sp3->first = "apple";
sp3->second = 1; // 等價于 sp3.opertor->()->second = 1;cout << sp3->first << " " << sp3->second << endl;
  • 智能指針的拷貝問題
// 智能指針的拷貝問題
int main()
{SmartPtr<int> sp1(new int(1));SmartPtr<int> sp2(sp1);return 0;
}

在這里插入圖片描述

vector / list.… 需要深拷貝,它們都是利用資源存儲數據,資源是自己的。拷貝時,每個對象各自一份資源,各管各的,所以深拷貝。

智能指針 / 迭代器… 期望的是淺拷貝
資源不是自己的,代為持有,方便訪問修改數據。他們拷貝的時候期望的指向同一資源,所以淺拷貝。而且智能指針還要負責釋放資源。

itertor it = begin();

2. C++ 98 標準庫中的 auto_ptr:

auto_ptr 管理權轉移,被拷貝的對象把資源管理權轉移給拷貝對象,導致被拷貝對象懸空
注意:在使用auto_ptr 過后不能訪問對象,否則就出現空指針了。很多公司禁止使用它,因為他很坑!

 // 智能指針的拷貝問題
// 1. auto_ptr 管理權轉移,被拷貝的對象把資源管理權轉移給拷貝對象,導致被拷貝對象懸空
// 注意:在使用auto_ptr 過后不能訪問對象,否則就出現空指針了。很多公司禁止使用它,因為他很坑!
int main()
{std::auto_ptr<int> sp1(new int(1));std::auto_ptr<int> sp2(sp1);*sp2 += 10;// 懸空*sp1 += 10;return 0;
}

auto_ptr 的實現:

namespace hd
{template<class T>class auto_ptr {public:// RAIIauto_ptr(T* ptr = nullptr):_ptr(ptr){}// ap2(ap1)auto_ptr(auto_ptr<T>& ap){_ptr = ap._ptr;ap._ptr = nullptr;}~auto_ptr(){if (_ptr) {std::cout << "delete: " << _ptr << std::endl;delete _ptr;                                                             }}// 像指針一樣使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

3. C++ 11 中的智能指針

boost 智能指針
scoped_ptr / scoped_array
shared_ptr / shared_array

C++ 11
unique_ptrscoped_ptr類似的
shared_ptrshared_ptr類似的

unique_ptr
禁止拷貝,簡單粗暴,適合于不需要拷貝的場景
在這里插入圖片描述
賦值也禁掉了:
在這里插入圖片描述
unique_ptr:實現

namespace hd
{template<class T>class unique_ptr {public:// RAIIunique_ptr(T* ptr = nullptr):_ptr(ptr){}// ap2(ap1)unique_ptr(const unique_ptr<T>& ap) = delete;  // 禁掉拷貝構造// 賦值也要禁掉,賦值會生成默認成員函數,淺拷貝,也會出現問題unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;~unique_ptr(){if (_ptr) {std::cout << "delete: " << _ptr << std::endl;delete _ptr;}}// 像指針一樣使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

如果必須要拷貝用shared_ptr:
shared_ptr 允許自由拷貝,使用引用計數解決多次釋放的問題

引用計數: 記錄有幾個對象參與管理這個資源
在這里插入圖片描述
shared_ptr 實現:
使用靜態成員變量實現。

namespace hd
{template<class T>class shared_ptr {public:// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr){_count = 1;}// sp(sp1)shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;++_count;}~shared_ptr(){if (--_count == 0){std::cout << "delete:" << _ptr << std::endl;delete _ptr;}}int use_count(){return _count;}// 像指針一樣使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;static int _count;};template<class T>int shared_ptr<T>::_count = 0;
}

在這里插入圖片描述
中釋放了一個資源!
如果使用靜態成員屬于這個類,屬于這個類的所有對象
需求:每個資源配一個引用計數,而不是全部都是一個引用計數!

所以,一個資源配一個引用計數無論多少個對象管理這個資源,只有這一個計數對象!
怎么找到這個引用呢?每個對象存一個指向計數的指針!

namespace hd
{template<class T>class shared_ptr {public:// RAIIshared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(new int(1)){}// sp2(sp1)shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;// 拷貝時++計數++(*_pcount);}void release(){// 說明最后一個管理對象析構了,可以釋放資源了if (--(*_pcount) == 0){std::cout << "delete:" << _ptr << std::endl;delete _ptr;delete _pcount;}}// 賦值 sp1 = sp3;shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr) // 避免自己給自己賦值{release();_ptr = sp._ptr;_pcount = sp._pcount;// 拷貝時++計數++(*_pcount);}return *this;}~shared_ptr(){release();}int use_count(){return *_pcount;}// 像指針一樣使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;};}

在這里插入圖片描述

shared_ptr 的缺陷:

// shared_ptr 的缺陷
struct ListNode
{int _val;std::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;ListNode(int val = 0):_val(val),_next(nullptr),_prev(nullptr){}};int main()
{std::shared_ptr<ListNode> n1(new ListNode(10));std::shared_ptr<ListNode> n2(new ListNode(20));n1->_next = n2;n2->_prev = n1;//delete n1;//delete n2;return 0;
}

循環引用:

  1. 左邊的節點,是由右邊的節點_prev管著的,_prev析構,引用計數減到 0, 左邊的節點就是釋放
  2. 右邊節點中_prev 什么時候析構呢?右邊的節點被delete時,_prev 析構。
  3. 右邊節點什么時候delete呢?右邊的節點被左邊的節點的_next管著的,_next析構,右邊的節點就釋放了。
  4. _next 什么時候析構呢?_next 是左邊節點的成員,左邊節點 delete, _next 就析構了
  5. 左邊節點什么時候釋放呢?回調 1 點 又循環上去了

右邊節點釋放 -> _prev析構 -> 左邊節點的釋放 -> _next析構 -> 右邊節點釋放

所以這是 shared_ptr 特定場景下的缺陷, 只要有兩個shared_ptr 互相管理就會出現這樣的情況,所以即使用了智能指針,同樣可能導致內存的泄漏。

struct ListNode
{int _val;std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;ListNode(int val = 0):_val(val){}};int main()
{std::shared_ptr<ListNode> n1(new ListNode(10));std::shared_ptr<ListNode> n2(new ListNode(20));cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;//delete n1;//delete n2;return 0;
}

在這里插入圖片描述
weak_ptr 可以通過不增加引用計數的方式,避免這個問題。(存在單獨自己的 引用計數)
weak_ptr 不支持RAII, 不參與資源管理,不支持指針初始化,但是還是能起到指向你的作用
weak_ptr 的實現:

namespace hd
{template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;}weak_ptr<T>& operator=(const shared_ptr<T>& sp){  _ptr = sp.get(); // 用 get方法調原生指針}// 像指針一樣使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

shared_ptr 定制刪除器

template<class T>
struct DeleteArry
{void operator()(T* ptr){delete[] ptr;}
};// 定制刪除器
int main()
{std::shared_ptr<ListNode> p1(new ListNode(10));std::shared_ptr<ListNode[]> p2(new ListNode[10]); // 可以用數組的std::shared_ptr<ListNode> p2(new ListNode[10], DeleteArry<ListNode>()); // 用仿函數的對象去釋放!std::shared_ptr<FILE> p3(fopen("test.cpp", "r"), [](FILE* ptr) {fclose(ptr);  }); // 用lamada表達式也是可以的return 0;
}

在這里插入圖片描述

定制刪除器實現:

namespace hd
{template<class T>class shared_ptr{public:// function<void(T*)> _del = [](T* ptr) {delete ptr; };template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)), _del(del){}// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}// sp2(sp1)shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;// 拷貝時++計數++(*_pcount);}// sp1 = sp4// sp4 = sp4;// sp1 = sp2;shared_ptr<T>& operator=(const shared_ptr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;// 拷貝時++計數++(*_pcount);}return *this;}void release(){// 說明最后一個管理對象析構了,可以釋放資源了if (--(*_pcount) == 0){std::cout << "delete:" << _ptr << std::endl;//delete _ptr;_del(_ptr);delete _pcount;}}~shared_ptr(){// 析構時,--計數,計數減到0,release();}int use_count(){return *_pcount;}// 像指針一樣T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;std::function<void(T*)> _del = [](T* ptr) {delete ptr; };};}

4. 內存泄漏

什么是內存泄漏:內存泄漏指因為疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。內
存泄漏并不是指內存在物理上的消失,而是應用程序分配某段內存后,因為設計錯誤,失去了對
該段內存的控制,因而造成了內存的浪費。
內存泄漏的危害:長期運行的程序出現內存泄漏,影響很大,如操作系統、后臺服務等等,出現
內存泄漏會導致響應越來越慢,最終卡死。

void MemoryLeaks()
{// 1.內存申請了忘記釋放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2.異常安全問題int* p3 = new int[10];Func(); // 這里Func函數拋異常導致 delete[] p3未執行,p3沒被釋放.delete[] p3;
}

總結:

本文詳細介紹了智能指針的概念、使用和原理,從C++98的auto_ptr到C++11的unique_ptrshared_ptr,展示了智能指針在現代C++編程中的應用和發展。我們了解到RAII(資源獲取即初始化)的設計模式,它通過將資源管理封裝在對象的生命周期中,簡化了資源的獲取和釋放過程。文章還討論了智能指針的拷貝問題,特別是auto_ptr的缺陷和shared_ptr的循環引用問題,以及如何使用weak_ptr和定制刪除器來解決這些問題。

此外,文章還探討了內存泄漏的概念、原因和危害,以及如何在實際編程中避免這些問題。通過具體的例子和代碼,我們學習了如何使用智能指針來管理資源,確保資源在使用完畢后能夠被正確釋放,從而避免內存泄漏和其他潛在的資源管理問題。

總的來說,智能指針是C++中一個強大的特性,它不僅提高了代碼的安全性和效率,還使得資源管理變得更加簡單和直觀。通過本文的學習,讀者應該能夠更加自信地在C++項目中使用智能指針,編寫出更加健壯和可靠的軟件。

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

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

相關文章

汽車液態電池隔膜的作用

標簽: 汽車液態電池隔膜的作用; 聚乙烯(PE);聚丙烯(PP) 問題:汽車液態電池隔膜的作用? 汽車液態電池隔膜的作用 汽車液態電池中的隔膜是一個至關重要的組件,它在電池的性能、安全性和壽命方面起著關鍵作用。下面詳細講述隔膜的主要功能和作用: 1. 電化學隔離 隔…

【面試干貨】猴子吃桃問題

【面試干貨】猴子吃桃問題 1、實現思想2、代碼實現 &#x1f496;The Begin&#x1f496;點點關注&#xff0c;收藏不迷路&#x1f496; 猴子吃桃問題&#xff1a;猴子第一天摘下若干個桃子&#xff0c;當即吃了一半&#xff0c;還不癮&#xff0c;又多吃了一個 二天早上又將剩…

空調濾網拆洗夠不到如何處理

空調濾網拆洗夠不到如何處理 將插口插好&#xff0c;用空調外殼的開合力把濾網懟進去

牛客小白月賽94 解題報告 | 珂學家 | 茴字有36種寫法

前言 很久沒寫題解了&#xff0c;有幸參加了94小白月賽內測&#xff0c;反饋是很nice&#xff0c;AK場。 爭議的焦點在于哪題最難 D題E題(沒有F題)F題(沒有E題) 你選哪題呢&#xff1f; 題解 歡迎關注 珂朵莉 牛客周賽專欄 珂朵莉 牛客小白月賽專欄 A. 小苯的九宮格 思路…

手機相冊的照片徹底刪除了怎么恢復?刪除照片恢復的5種方法

在數字化時代&#xff0c;手機相冊里裝滿了我們的生活點滴和珍貴回憶。然而&#xff0c;一不小心就可能誤刪那些意義非凡的照片。別擔心&#xff0c;今天小編就給大家介紹5種恢復誤刪照片的方法&#xff0c;讓你的回憶不再丟失&#xff01; 方法一&#xff1a;相冊App的“最近刪…

Docker Compose使用

Docker-Compose是什么 docker建議我們每一個容器中只運行一個服務,因為doker容器本身占用資源極少&#xff0c;所以最好是將每個服務單獨分割開來&#xff0c;但是這樣我們又面臨了一個問題&#xff1a; 如果我需要同時部署好多個服務&#xff0c;難道要每個服務單獨寫Docker…

P4097 【模板】李超線段樹 / [HEOI2013] Segment 題解

題意 有一個平面直角坐標系&#xff0c;總共 n n n 個操作&#xff0c;每個操作有兩種&#xff1a; 給定正整數 x 0 , y 0 , x 1 , y 1 x_0,y_0,x_1,y_1 x0?,y0?,x1?,y1? 表示一條線段的兩個端點。你需要在平面上加入這一條線段&#xff0c;第 i i i 條被插入的線段的標…

Photoshop插件(UXP)編寫過程中,如何更新sp-checkbox的選中狀態

?問題說明 sp-checkbox是uxpSpectrum UXP Widgets下的一個小組件&#xff0c;內置樣式大概是這樣&#xff1a; 那么&#xff0c;如果用js動態的改變選中的狀態&#xff0c;應該如何做呢&#xff1f; 如果直接是html來寫&#xff1a; <sp-checkbox checked>Checked<…

特斯拉FSD的「端到端」到底能不能成?

引言 近年來&#xff0c;特斯拉的全自動駕駛&#xff08;Full Self-Driving&#xff0c;FSD&#xff09;技術備受關注&#xff0c;尤其是其「端到端」的AI軟件框架更是引發了廣泛討論。端到端技術到底是一條正確的路徑嗎&#xff1f;它能否真正實現完全自動駕駛&#xff1f;本…

LangChain 0.2 - 矢量存儲和檢索器

本文翻譯整理自&#xff1a;Vector stores and retrievers https://python.langchain.com/v0.2/docs/tutorials/retrievers/ 文章目錄 一、說明概念 二、文件三、Vector stores示例 四、Retrievers五、了解更多 一、說明 本教程將讓您熟悉 LangChain 的向量存儲和檢索器抽象。…

大語言模型LLM 相關知識匯總

大型語言模型&#xff08;LLM&#xff09;在設計和應用時需要遵守一系列的道德和法律標準&#xff0c;以確保不會輸出不當內容。以下是一些LLM通常不應該對外輸出的內容類型&#xff1a; 個人隱私信息&#xff1a;包括但不限于個人身份信息&#xff08;PII&#xff09;&#x…

Echarts 實現將X軸放在圖表頂部并且自動播放展示提示信息內容

文章目錄 需求分析效果預覽需求 如下圖所示,實現柱狀圖中反轉倒著繪制 分析 使用 ECharts 來實現對 Y 軸的倒序排序時,可以通過設置 yAxis 的 inverse 屬性為 true 來實現。以下是一個簡單的示例,演示了如何使用 ECharts 來創建一個柱狀圖,并將 Y 軸進行倒序排序:并且…

前綴和算法:提升編程效率的秘密武器(Java版)

本篇會加入個人的所謂魚式瘋言 ??????魚式瘋言:??????此瘋言非彼瘋言 而是理解過并總結出來通俗易懂的大白話, 小編會盡可能的在每個概念后插入魚式瘋言,幫助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能說的不是那么嚴謹.但小編初心是能讓更多人能接…

代碼審計--一道簡單的文件包含題目的多種利用方式

NO.1 傳統方法 首先來看下代碼 <?php error_reporting(0); if(isset($_GET["file"])){include($_GET["file"]); }else{highlight_file(__FILE__);phpinfo(); } ?>看完代碼后再來學習學習函數吧&#xff0c;畢竟菜啊&#xff01;&#xff01;&…

IronPython和C#交互

在C#環境中動態調用IronPython腳本&#xff0c;可以通過以下步驟實現&#xff1a; 安裝IronPython: 首先&#xff0c;確保你的項目中已經安裝了IronPython。可以通過NuGet包管理器來安裝IronPython。 創建IronPython運行環境: 在C#代碼中&#xff0c;你需要創建一個ScriptEngi…

NASA數據集——阿爾法噴氣式大氣實驗甲醛(HCHO)數據

Alpha Jet Atmospheric eXperiment Formaldehyde Data 簡介 阿爾法噴氣式大氣實驗甲醛數據 阿爾法噴氣式大氣實驗&#xff08;AJAX&#xff09;是美國國家航空航天局艾姆斯研究中心與 H211, L.L.C. 公司的合作項目&#xff0c;旨在促進對加利福尼亞、內華達和太平洋沿岸地區的…

【NOIP2014普及組復賽】題4:子矩陣

題3&#xff1a;子矩陣 【題目描述】 給出如下定義&#xff1a; 1.子矩陣&#xff1a;從一個矩陣當中選取某些行和某些列交叉位置所組成的新矩陣&#xff08;保持行與列的相對順序&#xff09;被稱為原矩陣的一個子矩陣。 例如&#xff0c;下面左圖中選取第 2 、 4 2、4 2、…

vue項目中使用json編輯器

實現效果&#xff1a; 借助插件json-editor-vue3實現效果如圖一&#xff0c;如果嫌丑可以通過類名改一下樣式如圖二。 實現過程&#xff1a; 安裝插件&#xff1a;npm install json-editor-vue3 文檔鏈接&#xff1a;GitCode - 開發者的代碼家園 <script setup name&quo…

Golang發送POST請求并傳遞JSON數據

客戶端 package mainimport ("c02_get_param/common""fmt""zdpgo_resty" )func main() {// Create a Resty Clientclient : zdpgo_resty.New()// 設置字符串resp, err : client.R().SetHeader("Content-Type", "application/jso…

AcWing 3466. 清點代碼庫(STL:map,vector)

3466. 清點代碼庫 需要求有幾種不同數列&#xff0c;每種有多少個&#xff0c;可以想到用map。它的鍵是一個數列&#xff0c;可以把它放在vector里。也就是map<vector<int>,int> 要滿足要求的輸出序列&#xff0c;就要想把它放在其他容器&#xff0c;或數組里&…