【C++ 】智能指針:內存管理的 “自動導航儀”

目錄

一、引入

二、智能指針的兩大特性:

1、RAII

特點:

好處:

2、行為像指針

三、智能指針起初的缺陷:拷貝問題

四、幾種智能指針的介紹。

1、C++98出現的智能指針——auto_ptr

auto_ptr解決上述拷貝構造的問題:

2、boost庫

3、unique_ptr

4、shared_ptr

引用計數的實現:

賦值運算符的問題:(循環引用)

5、weak_ptr

特點:

解決循環引用問題:

五、C++智能指針的基本框架:

六、定制刪除器,以及包裝器的使用場景之一

七、內存泄漏:

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

2、內存泄漏的分類

八、關于C++智能指針的相關代碼:

std::unique_ptr

std::shared_ptr

std::weak_ptr


前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家

點擊跳轉到網站

一、引入

首先通過一個使用場景來引入智能指針,如下:

class HF
{
public:HF(int a, int b):_a(a),_b(b){cout << "HF(int a, int b)" << endl;};~HF(){cout << "~HF()" << endl;}
private:int _a = 0;int _b = 1;
};void fun()
{HF* h1 = new HF(1, 1);HF* h2 = new HF(1, 1);HF* h3 = new HF(1, 1);delete h1;delete h2;delete h3;
}int main()
{try{fun();}catch (exception& e){cout << e.what() << endl;}return 0;
} 

這里有一個類HF,一個子函數fun,在fun里面new了三個HF對象,然后delete,正常情況下delete會先調用析構函數,然后再釋放相應的資源:

二、智能指針的兩大特性:

智能指針的兩大特性:

1、RAII

2、行為像指針

1、RAII

是一種利用對象生命周期來控制程序資源(如內存、文件句柄、網絡連接、互斥量等等)的簡單技術。
(通俗來講,就是將資源交給一個類對象來管理,通過該類的構造函數交給對象。)

特點:

在對象構造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內始終保持有效,最后在對象析構的時候釋放資源。

好處:

(1)、不需要顯式地釋放資源,而是通過智能指針間接幫忙釋放
(2)、采用這種方式,對象所需的資源在其生命期內始終保持有效

2、行為像指針

智能指針實際也是一個類,要是行為像一個指針,即要重載解引用(*),箭頭(->),甚至有時還要重載方括號([ ])。

三、智能指針起初的缺陷:拷貝問題

首先我們實現一個簡易版智能指針:

new了一個日期類對象交給智能指針管理,智能指針對象存在期間,資源都是存在的,最后智能指針對象生命周期結束,調用析構函數釋放,同時釋放掉資源(delete);

template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }
private:T* _ptr;
};
struct Date
{int _year;int _month;int _day;
};
int main()
{SmartPtr<int> sp1(new int);*sp1 = 10;cout << *sp1 << endl;SmartPtr<Date> sparray(new Date);// 需要注意的是這里應該是sparray.operator->()->_year = 2018;// 本來應該是sparray->->_year這里語法上為了可讀性,省略了一個->sparray->_year = 2018;sparray->_month = 1;sparray->_day = 1;return 0;
}

但是當我們用對象sp1去拷貝構造sp2時:

此時就會報錯:

原因在于我們沒有實現拷貝構造,此時默認拷貝構造就是淺拷貝,這樣兩個對象的成員變量都會指向這份資源,最后調用析構函數時,就會對這份資源delete兩次,從而造成野指針的問題。如何解決這個問題,在第四點進行介紹。

四、幾種智能指針的介紹。

1、C++98出現的智能指針——auto_ptr

頭文件:memory

具體信息可以查看官網文檔。

auto_ptr解決上述拷貝構造的問題:

auto_ptr是直接將資源的管理權轉移,用對象sp1去拷貝構造sp2,那么就會將sp1的資源的管理權交給sp2管理,而sp1被置空。

大致處理方法如下:拷貝后將sp1置空就行了:

注意,C++11的移動語義也是資源的轉移,但和這里是不一樣的,移動語義是針對將亡值去轉移資源,而這里sp1不是將亡值。

這樣做是有些問題的,這里資源轉移后,sp1就懸空了,此時拷貝后就不能去訪問sp1,否則就會出現空指針的問題,所以很多公司都禁止使用auto_ptr;

2、boost庫

提到智能指針,就得提一下boost庫,boost庫是C++第三方庫,里面就有智能指針,而C++的智能指針就是從這個庫里面引入的,然后進行了略微修改。

3、unique_ptr

該智能指針解決拷貝構造的問題的方法就是:簡單粗暴,禁止拷貝,適用于不需要拷貝的場景。

底層實際就是將拷貝構造給delete了:

同時,賦值運算符重載也要禁掉,默認生成的賦值運算符重載也是淺拷貝。

4、shared_ptr

當遇到需要拷貝構造的場景時,就需要使用shared_ptr,shared_ptr解決拷貝構造的問題的方法是:引用計數,去解決多次析構的問題。

引用計數的實現:

引用計數:記錄當前有幾個對象參與管理這個資源,在某個對象析構時,就將引用計數減1,當最后一個對象析構時才去釋放資源。

要實現引用計數,就需要一份資源對應一個計數,有人會想到定義一個靜態成員count,但實則不行,因為靜態成員是屬于整個類的,屬于所有對象。管理一個資源的時候是可以解決的,但當第二個資源出現時,就不能適用了,因為不同資源之間的引用計數都是同一個靜態成員變量,所以會相互影響。

實際上的實現如下:

增加一個成員變量*pcount,即指向引用計數的指針,在構造的時候(即資源來了),就new一個計數給該指針,在拷貝構造發生的時候,除了使兩個對象指向同一個資源外,兩個對象的引用計數也要指向同一個,并且要記得把引用計數++一下,在某個對象析構時,就將引用計數減1,然后判斷是否為最后一個對象的析構,如果是的話就釋放資源。

賦值運算符的問題:(循環引用)

shared_ptr雖然解決了拷貝構造的問題,但正因為引用計數的這樣實現,又會造成賦值運算符重載后出現問題。

賦值運算符簡單重載:

為了分析這里的缺陷,我們引入一個場景:雙向鏈表的賦值:

這是雙向鏈表的簡單實現

因為會將鏈表資源交給智能指針管理,如下:

所以鏈表的定義中,成員next和prev的類型也應該是智能指針,不然在賦值的時候會出現類型不同的問題,正因為需要這樣設計,問題就來了。

一般情況上述實現是沒有問題的,但當執行下面兩句代碼后,問題就來了:

這是在鏈接兩個節點,鏈接完后就會這樣:

首先出現兩個節點分別由n1和n2指向,此時兩個節點的引用計數分別都是1,當執行n1->next = n2時,n2指向的節點的引用計數就會變成2;當執行n2->prve = n1時,n1指向的節點的引用計數就會變成2。

最后當析構鏈表時:

這樣就形成了一個閉環,導致這兩個節點的內存泄漏,這個問題也叫循環引用。當兩個shared_ptr互相引用就會出現循環引用的問題。

5、weak_ptr

為了解決shared_ptr的循環引用問題,所以引入了weak_ptr。

特點:

weak_ptr的特點:沒有引用計數,支持默認構造,構造函數的形參沒有指針,因為該智能指針不參與資源管理,但自身成員變量會有一個指針,但會被置空,weak_ptr的重點在于拷貝構造和賦值。

解決循環引用問題:

這里的不同是將鏈表的成員變量_next和_prev的類型變為weak_ptr,正因為weak_ptr沒有增加引用計數,所以在節點鏈接的時候,引用計數不會增加,所以節點會正常釋放。

五、C++智能指針的基本框架:

六、定制刪除器,以及包裝器的使用場景之一

七、內存泄漏:

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

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

2、內存泄漏的分類

C++中我們一般關心兩種分類:
(1)、堆內存泄漏(Heap leak)
堆內存指的是程序執行中依據須要分配通過malloc / calloc / realloc / new等從堆中分配的一
塊內存,用完后必須通過調用相應的 free或者delete 刪掉。假設程序的設計錯誤導致這部分
內存沒有被釋放,那么以后這部分空間將無法再被使用,就會產生Heap Leak。
(2)、系統資源泄漏
指程序使用系統分配的資源,比方套接字、文件描述符、管道等沒有使用對應的函數釋放
掉,導致系統資源的浪費,嚴重可導致系統效能減少,系統執行不穩定。

八、關于C++智能指針的相關代碼:

此小點內容來源于:豆包AI

#include <iostream>
#include <memory>// 自定義類
class MyClass {
public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }void doSomething() { std::cout << "Doing something..." << std::endl; }
};class B;class A {
public:std::shared_ptr<B> bPtr;~A() { std::cout << "A destructor" << std::endl; }
};class B {
public:std::weak_ptr<A> aPtr; // 使用 weak_ptr 避免循環引用~B() { std::cout << "B destructor" << std::endl; }
};void uniquePtrExample() {// 創建一個 unique_ptr 指向 MyClass 對象std::unique_ptr<MyClass> uniquePtr = std::make_unique<MyClass>();// 調用對象的成員函數uniquePtr->doSomething();// unique_ptr 不能被復制,但可以轉移所有權// std::unique_ptr<MyClass> anotherPtr = uniquePtr; // 錯誤,不能復制std::unique_ptr<MyClass> anotherPtr = std::move(uniquePtr);if (!uniquePtr) {std::cout << "uniquePtr is empty after move" << std::endl;}if (anotherPtr) {anotherPtr->doSomething();}
}void sharedPtrExample() {// 創建一個 shared_ptr 指向 MyClass 對象std::shared_ptr<MyClass> sharedPtr1 = std::make_shared<MyClass>();std::cout << "Shared pointer 1 use count: " << sharedPtr1.use_count() << std::endl;// 復制 shared_ptr,引用計數增加std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1;std::cout << "Shared pointer 1 use count after copy: " << sharedPtr1.use_count() << std::endl;std::cout << "Shared pointer 2 use count: " << sharedPtr2.use_count() << std::endl;// 調用對象的成員函數sharedPtr2->doSomething();// 釋放一個 shared_ptr,引用計數減少sharedPtr2.reset();std::cout << "Shared pointer 1 use count after reset sharedPtr2: " << sharedPtr1.use_count() << std::endl;
}void weakPtrExample() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->bPtr = b;b->aPtr = a;std::cout << "A use count: " << a.use_count() << std::endl;std::cout << "B use count: " << b.use_count() << std::endl;// 當 a 和 b 離開作用域時,對象會被正確銷毀
}int main() {std::cout << "=== Unique Ptr Example ===" << std::endl;uniquePtrExample();std::cout << std::endl;std::cout << "=== Shared Ptr Example ===" << std::endl;sharedPtrExample();std::cout << std::endl;std::cout << "=== Weak Ptr Example ===" << std::endl;weakPtrExample();std::cout << std::endl;return 0;
}    

在 C++ 里,手動管理動態分配的內存容易引發內存泄漏、懸空指針等問題。智能指針作為一種類模板,能有效管理動態分配的內存,避免這些問題的出現。C++ 標準庫提供了三種主要的智能指針:std::unique_ptrstd::shared_ptr?和?std::weak_ptr

std::unique_ptr

std::unique_ptr?屬于獨占式智能指針,它對所指向的對象擁有唯一的所有權。一旦?std::unique_ptr?被銷毀,其指向的對象也會隨之被自動銷毀。

?

在?uniquePtrExample?函數中:

?
  • 借助?std::make_unique?創建了一個?std::unique_ptr,它指向?MyClass?的一個對象。
  • 調用?doSomething?方法來使用這個對象。
  • 嘗試復制?std::unique_ptr?會引發編譯錯誤,因為它不允許復制,不過可以使用?std::move?轉移所有權。
  • 轉移所有權之后,原?std::unique_ptr?變為空。
std::shared_ptr

std::shared_ptr?是共享式智能指針,多個?std::shared_ptr?能夠指向同一個對象。它采用引用計數來管理對象的生命周期,當引用計數變為 0 時,對象就會被銷毀。

?

在?sharedPtrExample?函數中:

?
  • 利用?std::make_shared?創建了一個?std::shared_ptr,它指向?MyClass?的一個對象。
  • 通過?use_count?方法可以查看當前的引用計數。
  • 復制?std::shared_ptr?會使引用計數增加。
  • 調用?reset?方法可以釋放?std::shared_ptr,從而使引用計數減少。
std::weak_ptr

std::weak_ptr?是弱引用智能指針,它不擁有對象的所有權,只是對?std::shared_ptr?所管理的對象進行弱引用。std::weak_ptr?主要用于解決?std::shared_ptr?的循環引用問題。

?

在?weakPtrExample?函數中:

?
  • 定義了?A?和?B?兩個類,其中?A?類包含一個?std::shared_ptr<B>?成員,B?類包含一個?std::weak_ptr<A>?成員。
  • 創建了?A?和?B?的?std::shared_ptr?對象,并相互引用。
  • 由于?B?類使用了?std::weak_ptr,所以不會出現循環引用,當?a?和?b?離開作用域時,對象能夠被正確銷毀。

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

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

相關文章

Java多線程實現之線程池詳解

Java多線程實現之線程池詳解 一、線程池的基本概念1.1 為什么需要線程池1.2 線程池的核心思想 二、Java線程池的實現2.1 Executor框架2.2 ThreadPoolExecutor構造參數 三、常見線程池類型3.1 FixedThreadPool3.2 CachedThreadPool3.3 SingleThreadExecutor3.4 ScheduledThreadP…

解碼美元-黃金負相關:LSTM-Attention因果發現與黃金反彈推演

摘要&#xff1a;本文采用時間序列分析框架與自然語言處理&#xff08;NLP&#xff09;技術&#xff0c;對黃金與美元指數的負相關關系進行量化拆解。通過構建包含宏觀經濟因子、市場情緒指標及地緣風險的三維分析模型&#xff0c;揭示當前貴金屬市場的核心驅動邏輯&#xff0c…

Asp.Net Core SignalR導入數據

文章目錄 前言一、安裝包二、使用步驟1.實現SignalR Hub服務&#xff1a;2.實現CSV文件解析及數據導入服務3.控制器4.前端實現&#xff08;vue&#xff09; 三、關鍵技術點說明總結 前言 導入CSV文件中的數據到數據庫&#xff0c;使用CsvHelper解析CSV文件&#xff0c;SqlBulk…

Modern C++(四)聲明

4、聲明 聲明是將名字引入到cpp程序中&#xff0c;不是每條聲明都聲明實際的東西。定義是足以使該名字所標識的實體被使用的聲明。聲明包含以下幾種&#xff1a; 函數定義模板聲明模板顯式實例化模板顯式特化命名空間定義鏈接說明屬性聲明&#xff08;C11&#xff09;空聲明&…

目標檢測yolo算法

yolov5s&#xff1a; 從github官網下載yolov5的算法之后&#xff0c;配置好環境&#xff08;pycharm安裝包-CSDN博客&#xff09;&#xff0c;再下載權重文件&#xff0c;比如默認的yolov5s.pt&#xff1b; 運行當前文件&#xff08;detect.py&#xff09;&#xff0c;就能看…

一個超強的推理增強大模型,開源了,本地部署

大家好&#xff0c;我是 Ai 學習的老章 前幾天介紹了MOE 模型先驅 Mistral 開源的代碼 Agent 大模型——mistralai/Devstral-Small-2505 今天一起看看 Mistral 最新開源的推理大模型——Magistral Magistral 簡介 Mistral 公司推出了首個推理模型 Magistral 及自研可擴展強…

MySQL體系架構解析(五):讀懂MySQL日志文件是優化與故障排查的關鍵

MySQL文件 日志文件 在服務器運行過程中&#xff0c;會產生各種各樣的日志&#xff0c;比如常規的查詢日志&#xff0c;錯誤日志、二進制日志、 redo 日志和 Undo 日志等&#xff0c;日志文件記錄了影響 MySQL 數據庫的各種類型活動。 常見的日志文件有&#xff1a;錯誤日志…

湖南省網絡建設與運維賽項競賽規程及樣題

湖南省職業院校技能競賽樣題 賽題說明 一、競賽內容 “網絡建設與運維”競賽共分三個部分&#xff0c;其中&#xff1a; 第一部分&#xff1a;職業規范與素養 &#xff08; 5 分&#xff09; 第二部分&#xff1a;網絡搭建及安全部署項目 &#xff08; 50 分&#xff09…

華為云Flexus+DeepSeek征文 | 基于華為云ModelArts Studio搭建AnythingLLM聊天助手

華為云FlexusDeepSeek征文 | 基于華為云ModelArts Studio搭建AnythingLLM聊天助手 引言一、ModelArts Studio平臺介紹華為云ModelArts Studio簡介ModelArts Studio主要特點 二、AnythingLLM介紹AnythingLLM 簡介AnythingLLM主要特點AnythingLLM地址 三、安裝AnythingLLM應用下載…

板凳-------Mysql cookbook學習 (十--5)

6.11 計算年齡 2025年6月11日星期三 --創建表、初始化數據 drop table if exists sibling; create table sibling (name char(20),birth date );insert into sibling (name,birth) values(Gretchen,1942-04-14); insert into sibling (name,birth) values(Wilbur,1946-11-28)…

SAP RESTFUL接口方式發布SICF實現全路徑

其他相關資料帖可參考&#xff1a; https://blog.csdn.net/woniu_maggie/article/details/146210752 https://blog.csdn.net/SAPmatinal/article/details/134349125 https://blog.csdn.net/weixin_44382089/article/details/128283417 【業務場景】 外部系統不想通過RFC (需…

在windows中安裝或卸載nginx

首先在nginx的安裝目錄下cmd查看nginx的版本&#xff1a; 在看windows的服務中是否nginx注冊為服務了 如果注冊了服務就先將服務卸載了 在nginx的安裝目錄cmd執行命令 NginxService.exe uninstall “NginxService”是對應的注冊的服務名稱 關閉所有的相關nginx的服務這個也…

FaceFusion 技術深度剖析:核心算法與實現機制揭秘

在 AI 換臉技術蓬勃發展的浪潮中&#xff0c;FaceFusion 憑借其出色的換臉效果和便捷的操作&#xff0c;成為眾多用戶的首選工具。從短視頻平臺上的創意惡搞視頻&#xff0c;到影視制作中的特效合成&#xff0c;FaceFusion 都展現出強大的實用性。而這一切的背后&#xff0c;是…

2. Web網絡基礎 - 協議端口

深入解析協議端口與netstat命令&#xff1a;網絡工程師的實戰指南 在網絡通信中&#xff0c;協議端口是服務訪問的門戶。本文將全面解析端口概念&#xff0c;并通過netstat命令實戰演示如何監控網絡連接狀態。 一、協議端口核心知識解析 1. 端口號的本質與分類 端口范圍類型說…

嵌入式學習筆記 - freeRTOS vTaskPlaceOnEventList()函數解析

vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); 函數第一個參數為消息隊列等待插入鏈表&#xff0c; void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait ) { configASSERT( pxEventList ); /…

Ubuntu 配置使用 zsh + 插件配置 + oh-my-zsh 美化過程

Ubuntu 配置使用 zsh 插件配置 oh-my-zsh 美化過程 引言zsh 安裝及基礎配置oh-my-zsh 安裝及美化配置oh-my-zsh 安裝主題美化配置主題自定義主題 插件安裝及配置官方插件查看及啟用插件安裝 主題文件備份.zshrcre5et_self.zsh-theme 同步發布在個人筆記Ubuntu 配置使用 zsh …

Xilinx FPGA 重構Multiboot ICAPE2和ICAPE3使用

一、FPGA Multiboot 本文主要介紹基于IPROG命令的FPGA多版本重構&#xff0c;用ICAP原語實現在線多版本切換。需要了解MultiBoot Fallback點擊鏈接。 如下圖所示&#xff0c;ICAP原語可實現flash中n1各版本的動態切換&#xff0c;在工作過程中&#xff0c;可以通過IPROG命令切…

springMVC-11 中文亂碼處理

前言 本文介紹了springMVC中文亂碼的解決方案&#xff0c;同時也貼出了本人遇到過的其他亂碼情況&#xff0c;可以根據自身情況選擇合適的解決方案。 其他-jdbc、前端、后端、jsp亂碼的解決 Tomcat導致的亂碼解決 自定義中文亂碼過濾器 老方法&#xff0c;通過javaW…

mysql-innoDB存儲引擎事務的原理

InnoDB 存儲引擎支持 ACID 事務&#xff0c;其事務機制是通過 Redo Log&#xff08;重做日志&#xff09;、Undo Log&#xff08;回滾日志&#xff09; 和 事務日志系統 來實現的。下面詳細解析 InnoDB 事務的工作原理。 1.事務的基本特性&#xff08;ACID&#xff09; 特性描…

在GIS 工作流中實現數據處理

通過將 ArcPy 應用于實際的 GIS 工作流&#xff0c;我們可以高效地完成數據處理任務&#xff0c;節省大量時間和精力。接下來&#xff0c;本文將結合具體案例&#xff0c;詳細介紹如何運用 ArcPy 實現 GIS 數據處理的全流程。 數據讀取與合并 假設我們有多個 shapefile 文件&a…