【C++11(三)】智能指針詳解--RAII思想循環引用問題

💓博主CSDN主頁:杭電碼農-NEO💓
?
?專欄分類:C++從入門到精通?
?
🚚代碼倉庫:NEO的學習日記🚚
?
🌹關注我🫵帶你學習C++
? 🔝🔝


在這里插入圖片描述

C++11

  • 1. 前言
  • 2. 為什么要有智能指針?
  • 3. RAII思想以及智能指針的設計
  • 4. C++智能指針的發展歷史
  • 5. shared_ptr模擬實現
  • 6. shared_ptr的循環引用問題
  • 7. 定制刪除器
  • 8. 總結以及拓展

1. 前言

相信學C++的同學或多或少的聽說過
智能指針這個詞,博主剛聽見這個詞時
,覺得它應該很復雜,并且很高大上,但不
管是多牛的東西,都是人寫出來的,是可
學習的!不要懷著害怕的心理來學習它

本章重點:

本篇文章著重講解智能指針的發展歷史
中出現過的auto_ptr,unique_ptr以及主
角shared_ptr.并且會介紹什么是RAII思想
以及為什么要有智能指針這一話題,最后
會給大家分析shared_ptr的循環引用問題
以及定制刪除器的基本概念


2. 為什么要有智能指針?

在寫代碼時,我們經常在堆上申請空間
但是偶爾會忘記釋放空間,會造成內存
泄漏問題,當然,這不是最重要的,在某些
場景下即使你釋放了也會有問題:

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0錯誤");return a / b;
}
void Func()
{// 1、如果p1這里new 拋異常會如何?// 2、如果p2這里new 拋異常會如何?// 3、如果div調用這里又會拋異常會如何?int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

在上面代碼的這種場景中,不管是使用
new還是調用div函數都有拋異常的風險
并且程序一旦拋異常就會直接跳到catch
處,所以上面的代碼一旦拋異常就代表著
delete p1和p2并不會執行,也就會出現
內存泄漏的問題!這個問題不使用智能
指針是很難解決的!!!


3. RAII思想以及智能指針的設計

  1. RAII思想

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

這種做法有兩種好處:

  • 不需要顯式地釋放資源
  • 對象所需的資源在其生命期內始終有效
  1. 智能指針的基本設計

現在我們來寫一個類,構造函數的
時候創造資源,析構函數的時候釋放
資源,當對象出了作用域會自動調用析構!

// 使用RAII思想設計的SmartPtr類
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr): _ptr(ptr)
{}
~SmartPtr()
{if(_ptr!=nullptr)delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:T* _ptr;
};

現在我們來使用一下它:

SmartPtr<int> sp1(new int(10));
*sp = 20;

當然,重載了->是給自定義類型用的


4. C++智能指針的發展歷史

首先,我們要清楚智能指針的一個大坑
那就是當一個指針賦值給另外一個指針
時,我們需要的是淺拷貝,因為我們就是想
讓兩個指針指向同一塊空間,但是指向了
同一塊空間就會有析構函數調用兩次的風險
由于這一個大坑,智能指針進行了很多次迭代

  1. 在C++98的時候就已經在庫中實現
    了智能指針了,它就是 auto_ptr

在這里插入圖片描述

既然智能指針是隨著歷史不斷發展的
就證明它前面的版本寫的不咋滴[doge]
事實也是如此,auto_ptr是這樣實現的,
既然有析構兩次的風險,那么當我把A
指針賦值給B指針后,A指針就銷毀不能用
了,對于不了解auto_ptr的人來說這無疑是
一個巨大的風險!

auto_ptr<int> ap1(new int(10));
auto_ptr<int> ap2(ap1);
//此時ap1已經失效了!
  1. 有了這一大坑后,C++11推出了全新
    的智能指針: unique_ptr

在這里插入圖片描述

unique_ptr的做法比auto_ptr還絕
智能指針不是拷貝有問題嗎?那么
unique_ptr就禁用了拷貝和賦值,
很顯然這也是一個坑,但是在實際
場景下,unique_ptr至少還能被用到
但auto_ptr是很多公司明令禁止使用的!

unique_ptr<int> up1(new int(10));
unique_ptr<int> up2(up1);//這里會直接報錯
  1. 經過兩次失敗的智能指針后,C++11
    還推出了今天的主角: shared_ptr

在這里插入圖片描述

shared_ptr可堪稱完美的智能指針
也是實際中使用的最多的智能指針
它采用的是引用計數的思想,當指向
這份空間的計數是1時才析構,大于1
時就將計數減一,非常的優雅!

由于智能指針在面試時讓手撕的概率很大
所以我們會模擬實現它


5. shared_ptr模擬實現

我們使用引用計數的方式來實現
shared_ptr,也就是在原先代碼的
基礎上增加一個int*成員變量來保存
還有幾個指針指向當前空間!

template<class T>
class Smart_Ptr //實現的C++11的shared_ptr版本
{
public:Smart_Ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}~Smart_Ptr(){Release();}Smart_Ptr(const Smart_Ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount){Addcount();}Smart_Ptr<T>& operator=(const Smart_Ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;Addcount();}return *this;}void Release(){if (--(*_pcount) == 0)//銷毀最后一個變量時才釋放資源{delete _ptr;delete _pcount;delete _pmtx;}}void Addcount(){(*_pcount)++;}void Subcount(){Release();private:T* _ptr;int* _pcount;
};

我們將計數++賀計數- -特意的提出來
這是因為很多場景下都需要這兩個函數.
當計數不為1時就- -計數,當計數為一才
釋放資源,并且這樣寫的好處是相同類型
的指針對象即使指向不同的空間也不會
出錯,相反,使用static定義成員指針變量
就會出現上面的這種問題!


6. shared_ptr的循環引用問題

請看下面的代碼運行會崩潰:

struct ListNode
{int _data;shared_ptr<ListNode> prev;shared_ptr<ListNode> next;~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);node1->next = node2;node2->prev = node1;return 0;
}

為啥會崩潰?下面我用畫圖加文字
的方式幫大家分析一下此問題:

在這里插入圖片描述

現在來進一步分析:當main函數調用完,
node2會先析構,但是此時引用計數是2
所以不會釋放空間而是將計數變為1.
然后node1再析構,同上,它的引用計數
也減為一,但是這兩份空間并不會釋放,
因為要node2的prev釋放后,node1的空間
才會釋放,那node2的prev什么時候釋放?
答案是node2這份空間釋放了才會釋放
prev,那么node2這份空間什么時候釋放?
答案是node1的next釋放了它才釋放,這
就形成了一個死循環,我等你釋放了我才
能釋放,對方也在等我釋放了對方才能
釋放,這就是"循環引用問題"

最好的解決方案就是在使用智能指針
的時候跳過這個坑,不用將智能指針和
這種場景一起使用!!!

在這里插入圖片描述


7. 定制刪除器

使用智能指針時可能會遇見下面的問題:

shared_ptr<int> sp1(new int[10]);

當變量出作用域銷毀時即報錯
因為new []對應的是delete [].
然而庫中寫法并不能識別有沒有[]

還有一些問題:

shared_ptr<FILE> sp3(fopen("Test.cpp", "r"));

此時智能指針管理的對象并不是堆上
開辟的空間,delete完全沒法用,此時需
要使用fclose,所以定制刪除器非常重要

在這里插入圖片描述

在構造函數的地方可以傳入一個定制
刪除器,也就是一個函數對象,此函數
中有對應的刪除方法,請看下面的代碼:

shared_ptr<int> sp2(new int[10], [](int* ptr) {delete[] ptr; });
shared_ptr<FILE> sp3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });

注:定制刪除器屬于了解的部分


8. 總結以及拓展

智能指針在面試中是常客,經常會被
問到發展歷史和shared_ptr的手撕,
學到這里后,C++的所有重要的知識
差不多已經完結了,后面文章更新會慢一點

拓展:weak_ptr的拓展閱讀

既然weak_ptr可以解決shared_ptr的
循環引用問題,那么什么是weak_ptr?
有興趣的同學可以閱讀下面這篇文章:

weak_ptr詳解


🔎 下期預告:C++異常的處理方式🔍

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

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

相關文章

30.如何在Spring所有Bean創建完后做擴展?

如何在Spring所有Bean創建完后做擴展? 哪里才算所有的bean創建完了。 首先是所有的配置bean會注冊成BeanDefinition 然后根據BeanDefinition進行循環調用一個一個的getBean進行生產。 循環完所有的BeanDefiniton,通過BeanFactory的getBean方法生成所有的Bean 這個循環結…

LightDB - 支持substring_index 函數[mysql兼容]

從 23.4 版本開始&#xff0c; LightDB 支持 mysql 的substring_index 函數。下面對這個函數進行介紹 substring_index(str, delim, count ) 這個函數用于從指定字符串str中返回到達分隔符delim出現次數(count)之前的子字符串。。具體見之后用例&#xff1a; mysql 中介紹&a…

【BUG】微信小程序image不會隨著url動態變化

問題描述&#xff1a; 第一次打開界面&#xff0c;顯示的是默認頭像而不是用戶頭像&#xff0c;似乎image里面的src只要第一次有值就不會再更新了 解決 不要給src里面的變量設置初始值&#xff0c;而是直接賦空值

信息安全、網絡安全和數據安全的相互關系

最近正在開展安全方面的相關工作&#xff0c;因此就對這些概念做了一些分析&#xff0c;參考各種介紹和書籍&#xff0c;結合自身的認識&#xff0c;總結起來如下&#xff0c;信息安全、網絡安全、數據安全和基礎設施安全的關系究竟是什么&#xff0c;信息安全概念最大&#xf…

DevOps搭建(七)-安裝Jenkins詳細步驟

這里我們用Docker進行安裝 1、拉取Jenkins鏡像 Jenkins download and deployment 選擇LTS長期支持的版本,接著點擊Docker鏈接進入 找到上面的版本,并copy拉取鏡像的命令 docker pull jenkins/jenkins:2.426.1-lts 2、docker-compose安裝Jenkins 首先創建安裝目錄/home/f…

STM32 cubeMX 呼吸燈實驗

文章代碼使用 HAL 庫。 文章目錄 一、1.PWM原理二、LED 原理圖三、使用cubemx 配置 led四、PWM 相關函數五、PWM占空比占空比計算六、PWM 呼吸燈重要代碼總結 呼吸燈 一、1.PWM原理 PWM全稱為脈沖寬度調制&#xff08;Pulse Width Modulation&#xff09;&#xff0c;是一種常…

擁有大量蝦皮買家號有哪些好處

擁有眾多Shopee買家賬號&#xff0c;無疑是賣家們獲取極大優勢的一項策略。多賬號的運用不僅有助于賣家在Shopee平臺上獲得更為豐富的流量&#xff0c;更能夠在關鍵詞排名和銷售表現等方面為其帶來顯著提升。 首先&#xff0c;多個Shopee買家賬號的靈活運用&#xff0c;使賣家能…

前后端(JAVA)實現AES對稱加解密方式

文章目錄 前后端&#xff08;JAVA&#xff09;實現AES對稱加解密方式1 對稱加密分類以及概括1.1 加密安全等級 DES < 3DES < AES < RC1.2 DES1.3 3DES1.4 AES1.5 RC 2 前后端實現AES對稱加解密方式3 后端AES對稱加解密&#xff08;ECB和CBC模式&#xff09;工具類4 前…

【Python百寶箱】從傳感器到云端:深度解析Python在物聯網中的多面應用

邁向智能未來&#xff1a;Python與物聯網生態系統的完美融合 前言 隨著物聯網技術的不斷發展&#xff0c;Python作為一種靈活且強大的編程語言&#xff0c;逐漸成為物聯網開發的重要工具之一。本文將深入探討物聯網領域中常用的Python庫和框架&#xff0c;涵蓋了從輕量級通信…

JavaScript <有道翻譯之數據解密‘23年12月06日版‘>--案例(三)

前言: 記得上半年還是去年,有道翻譯還是直接返回明文數據;現在也跟著,用接口返回加密數據了; 娛樂一下,破他的密文數據... 成品效果圖: js部分: 對于找他的密文數據有點費時,針對密文--->搜他地址和啟動器不是特別容易,輾轉多時(搜:descrypt/json.parse 結合使用更快),有圖…

通訊錄實現

下方是頭文件的代碼 #define _CRT_SECURE_NO_WARNINGS #include <assert.h> #include<stdio.h> #include<string.h> #include<stdlib.h>#define NAME_MAX 20 #define SEX_MAX 6 #define TELE_MAX 12 #define ADDR_MAX 30 #define MAX 100 #define D…

swing快速入門(四)

注釋很詳細&#xff0c;直接上代碼 上一篇 增加內容 流式布局范例 import java.awt.*;public class swing_test_2{public static void main(String[] args){//創建一個窗口對象Frame framenew Frame("test");//設置窗口大小frame.setSize(800,800);//這里演示的是…

Gateway全局異常處理及請求響應監控

前言 我們在上一篇文章基于壓測進行Feign調優完成的服務間調用的性能調優&#xff0c;此時我們也關注到一個問題&#xff0c;如果我們統一從網關調用服務&#xff0c;但是網關因為某些原因報錯或者沒有找到服務怎么辦呢&#xff1f; 如下所示&#xff0c;筆者通過網關調用acc…

中小企業管理者如何培育團隊精神?

某石油工程有限公司總經理曾提問&#xff1a;“作為中小企業的管理者如何才能更好的激發團隊精神呢&#xff1f;” 每個企業都向往和號召團隊精神&#xff0c;但是往往事與愿違。在各種羨慕嫉妒恨的情緒影響下&#xff0c;難免會產生一些落差&#xff0c;影響到團隊精神。 所…

超聲波清洗機會損傷物品嗎?一文明白超聲波清洗機有哪些優點

正確使用超聲波清洗機且買對超聲波清洗機是不會對清洗物品造成傷害的&#xff01; 一、超聲波清洗機工作原理是如何的&#xff1f; 超聲波清洗機的工作原理是利用超聲波產生的空化振動來清潔物體。當超聲波在清洗液中傳播時&#xff0c;它會產生微小的氣泡和振動&#xff0c;這…

論jenkins的使用方法(初步)

&#x1f4d1;打牌 &#xff1a; da pai ge的個人主頁 &#x1f324;?個人專欄 &#xff1a; da pai ge的博客專欄 ??寶劍鋒從磨礪出&#xff0c;梅花香自苦寒來 目錄 &#x1f4d1;什么是持續集成&…

1-1、Java概述

語雀原文鏈接 文章目錄 1、Java發展2、Java體系結構3、Java特點 1、Java發展 1990年&#xff0c;Sun公司(Stanford University Network,斯坦福大學網絡公司)詹姆斯高斯林推出的一門語言最開始注冊的名字oak語言(橡樹)&#xff0c;重名了被迫改成Java2009年Sun公司被甲骨文Ora…

Docker 容器中使用 Docker - DinD 和 DooD

突然間研究這個來的緣由是正在從 Jenkins 往 Harness 的過度, 而完全用命令來構建 Docker 鏡像變得不一樣了。在 Jenkins 中 Agent 本身也是一個 Docker Daemon, 所以 Docker 命令執行無障礙&#xff0c;而 Harness 的所謂的 Agent 就是一個個的運行在 Kubernetes 中的 Docker …

error:gmapping

– Could not find the required component ‘gmapping’. The following CMake error indicates that you either need to install the package with the same name or change your environment so that it can be found. CMake Error at /opt/ros/kinetic/share/catkin/cmake…

logstash插件簡單介紹

logstash插件 輸入插件(input) Input&#xff1a;輸入插件。 Input plugins | Logstash Reference [8.11] | Elastic 所有輸入插件都支持的配置選項 SettingInput typeRequiredDefaultDescriptionadd_fieldhashNo{}添加一個字段到一個事件codeccodecNoplain用于輸入數據的…