C++進階篇8---智能指針

一、引言

為什么需要智能指針?

在上一篇異常中,關于內存釋放,我們提到過一個問題---當我們申請資源之后,由于異常的執行,代碼可能直接跳過資源的釋放語句到達catch,從而造成內存的泄露,對于這種情況,我們當時的解決方案是在拋出異常后,我們先對異常進行捕獲,將資源釋放,再將異常拋出,但這樣做會使得代碼變得很冗長,那有沒有什么辦法能讓它自動釋放內存資源呢?用智能指針

什么是智能指針?

說到自動釋放資源,是不是有點熟悉,我們在學習創建類對象時,就知道當類對象的生命周期結束后,系統會自動調用它的析構函數,完成資源的釋放,那么我將指針放入這樣一個類對象中,將釋放資源的工作交給析構函數,只要該對象生命周期結束,那么就釋放該資源,如此就不用在關心資源的釋放問題,只要函數棧幀銷毀,即該對象被銷毀,資源就會自動釋放,這就叫智能指針。

智能指針的使用和原理

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

2.具有指針的行為,可以解引用,也可以通過->去訪問所指空間中的內容

下面寫一個簡單的智能指針

namespace zxws
{template<class T>class smart_ptr{public:smart_ptr(T* ptr = nullptr):_ptr(ptr){}~smart_ptr(){cout << "delete _ptr" << endl;delete _ptr;_ptr = nullptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

但是上面這個智能指針有個嚴重的問題,一旦有兩個對象同時指向同一個資源,那么析構函數就會被調用兩次,即資源要被釋放兩次,會報錯,如下


二、庫中的智能指針

C++官方給出了3個智能指針

1.auto_ptr

auto_ptr:管理權轉移的思想,即一個資源只能有一個指針能對它進行管理,其他的指向這一資源的指針均為空,實現如下

namespace zxws
{template<class T>class auto_ptr{public:auto_ptr(T* ptr = nullptr):_ptr(ptr){}//管理權限的轉移auto_ptr(const auto_ptr& tmp):_ptr(tmp._ptr){tmp._ptr = nullptr;}auto_ptr& operator=(const auto_ptr& tmp){if (this != &tmp)//注意自己給自己賦值的情況不需要處理,否則會出問題{if (_ptr)//釋放當前對象中資源delete _ptr;//管理權限轉移_ptr = tmp._ptr;tmp._ptr = nullptr;}return *this;}~auto_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

?2.unique_ptr

unique_ptr:簡單粗暴的防拷貝,即一個指針只能被初始化一次,且只能用不同的資源初始化

實現如下

namespace zxws
{template<class T>class unique_ptr{public:unique_ptr(T* ptr = nullptr):_ptr(ptr){}//將拷貝構造和賦值重載直接ban掉unique_ptr(const unique_ptr& tmp) = delete;unique_ptr& operator=(const unique_ptr& tmp) = delete;~unique_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

3.shared_ptr

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

具體原理如下

1. shared_ptr在其內部,給每個資源都維護了著一份計數,用來記錄該份資源被幾個對象共享
2. 在對象被銷毀時(也就是析構函數調用),就說明自己不使用該資源了,對象的引用計數減一。
3. 如果引用計數是0,就說明自己是最后一個使用該資源的對象,必須釋放該資源
4. 如果不是0,就說明除了自己還有其他對象在使用該份資源,不能釋放該資源,否則其他對象就成野指針了。
實現如下
namespace zxws
{	template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}shared_ptr(const shared_ptr& tmp):_ptr(tmp._ptr),_pcount(tmp._pcount){(*_pcount)++;}shared_ptr& operator=(const shared_ptr& tmp){//這里注意自己給自己賦值的情況!!!//當引用計數為1時,就會出現將資源釋放后,在賦值的尷尬情況//用this!=&tmp也沒用,可能出現兩個不同對象指向同一塊資源的情況//所以用資源的地址來判斷最準確if (_ptr != tmp._ptr){release();_ptr = tmp._ptr;_pcount = tmp._pcount;(*_pcount)++;}return *this;}void release(){if (--(*_pcount)==0){delete _ptr;delete _pcount;_pcount = nullptr;_ptr = nullptr;}}~shared_ptr(){release();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count() const{return *_pcount;}private:T* _ptr;int* _pcount;};
}

那么引用計數,為什么要用指針開辟的空間,而不是成員變量或者靜態成員變量?

1、如果是成員變量,那么每一個shared_ptr對象都會有一個_pcount

2、如果是靜態成員變量,那么_pcount將屬于一個類

兩者都不能滿足我們的需求

關于shared_ptr還存在一個循環引用的問題,場景如下

當我們將循環鏈表的兩個結點連接起來的時候,就不會釋放結點空間,但是只要有一條邊沒鏈接就都能釋放,為什么???

而只連接一條邊,這個閉環就不復存在,所以兩個結點都能釋放,那如何解決這種情況?

針對這種情況,C++官方設計出了weak_ptr來和shared_ptr搭配使用,也就是說weak_ptr不增加shared_ptr的引用計數,且不參與資源的釋放

實現如下

namespace zxws
{	template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& tmp):_ptr(tmp.get()){}weak_ptr& operator=(const shared_ptr<T>& tmp){_ptr = tmp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

(上面三個智能指針的模擬實現是被簡化過的,功能不全,但是核心就是這些)

其中auto_ptr這個智能指針基本不用

上面寫的三個智能指針還有一個缺陷,就是釋放資源的delete寫死了,如果我們開的是一個數組,就需要用delete[],否則資源的釋放就會出現問題,所以就需要我們定制化它們的釋放資源的方式,根據前面的知識,我們可以給它傳一個釋放資源的仿函數,如下

template<class T>
struct Destroy {void operator()(T*_ptr){delete[] _ptr;}
};
template<class T, class D>
class shared_ptr
{//....
};
shared_ptr<int, Destroy<int>>p;

但是庫中只寫了一個模板參數

我們如果想實現和庫中一樣的效果,該怎么寫?

既然傳模板參數不行,我們只能傳函數對象了,用function包裝器和lambda表達式實現如下

namespace zxws
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}shared_ptr(T* ptr,function<void(T*)> del):_ptr(ptr),_pcount(new int(1)),_del(del){}shared_ptr(const shared_ptr& tmp):_ptr(tmp._ptr),_pcount(tmp._pcount),_del(tmp._del){(*_pcount)++;}shared_ptr& operator=(const shared_ptr& tmp){//這里注意自己給自己賦值的情況!!!//當引用計數為1時,就會出現將資源釋放后,在賦值的尷尬情況//用this!=&tmp也沒用,可能出現兩個不同對象指向同一塊資源的情況//所以用資源的地址來判斷最準確if (_ptr != tmp._ptr){release();_ptr = tmp._ptr;_pcount = tmp._pcount;_del = tmp._del;(*_pcount)++;}return *this;}void release(){if (--(*_pcount)==0){_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}}~shared_ptr(){release();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count() const{return *_pcount;}private:T* _ptr;int* _pcount;function<void(T*)>_del = [](T* ptr) {delete ptr; };};
}

其他幾個智能指針寫法類似,就不寫了。

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

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

相關文章

C# Winform 日志系統

目錄 一、效果 1.刷新日志效果 2.單獨日志的分類 3.保存日志的樣式 二、概述 三、日志系統API 1.字段 Debug.IsScrolling Debug.Version Debug.LogMaxLen Debug.LogTitle Debug.IsConsoleShowLog 2.方法 Debug.Log(string) Debug.Log(string, params object[]) …

數據結構之內部排序

目錄 7-1 直接插入排序 輸入格式: 輸出格式: 輸入樣例: 輸出樣例: 7-2 尋找大富翁 輸入格式: 輸出格式: 輸入樣例: 輸出樣例: 7-3 PAT排名匯總 輸入格式: 輸出格式: 輸入樣例: 輸出樣例: 7-4 點贊狂魔 輸入格式&#xff1a; 輸出格式&#xff1a; 輸入樣例&a…

RabbitMQ在國內為什么沒有那么流行?

MQ&#xff08;消息隊列&#xff09;的世界。MQ&#xff0c;就像是一個巨大的郵局&#xff0c;負責在不同服務或應用間傳遞消息。它可以幫助我們解耦系統&#xff0c;提高性能&#xff0c;還能做到異步處理和流量削峰。 基本使用 RabbitMQ是一個開源的消息代理和隊列服務器&a…

spring boot + uniapp 微信公眾號 jsapi 支付

后端支付類 package com.ruoyi.coupon.payment;import com.google.gson.Gson; import com.ruoyi.coupon.payment.dto.PayParamJsapiDto; import com.ruoyi.coupon.payment.dto.RefundParam; import com.ruoyi.coupon.service.ICouponConfigService; import com.wechat.pay.jav…

FFmpeg抽取視頻h264數據重定向

根據視頻重定向技術解析中的 截獲解碼視頻流的思路&#xff0c;首先需要解決如何輸出視頻碼流的問題。 目前只針對h264碼流進行獲取&#xff0c;步驟如下&#xff1a; 打開mp4文件并創建一個空文件用于存儲H264數據 提取一路視頻流資源 循環讀取流中所有的包(AVPacket),為…

redis中使用pipeline批量處理請求提升系統性能

在操作數據庫時&#xff0c;為了加快程序的執行速度&#xff0c;在新增或更新數據時&#xff0c;可以通過批量提交的方式來減少應用和數據庫間的傳輸次數&#xff1b;在redis中也有這樣的技術實現批量處理&#xff0c;也就是管道——Pipeline。它也是通過批量提交數據的方式來實…

線程安全3--wait和notify

文章目錄 wait and notify&#xff08;等待通知機制notify補充 wait and notify&#xff08;等待通知機制 引入wait notify就是為了能夠從應用層面上&#xff0c;干預到多個不同線程代碼的執行順序&#xff0c;這里說的干預&#xff0c;不是影響系統的線程調度策略&#xff08…

uni-app應用設置 可以根據手機屏幕旋轉進行 (橫/豎) 屏切換

首先 我們打開項目的 manifest.json 在左側導航欄中找到 源碼視圖 然后找到 app-plus 配置 在下面加上 "orientation": [//豎屏正方向"portrait-primary",//豎屏反方向"portrait-secondary",//橫屏正方向"landscape-primary",//橫屏…

第57天:django學習(六)

模版之過濾器 語法&#xff1a; {{obj|filter__name:param}} 變量名字|過濾器名稱&#xff1a;變量 default 如果一個變量是false或者為空&#xff0c;使用給定的默認值。否則&#xff0c;使用變量的值。例如&#xff1a; {{ value|default:"nothing"}} length …

IDEA啟動應用時報錯:錯誤: 找不到或無法加載主類 @C:\Users\xxx\AppData\Local\Temp\idea_arg_filexxx

IDEA啟動應用時報錯&#xff0c;詳細錯誤消息如下&#xff1a; C:\devel\jdk1.8.0_201\bin\java.exe -agentlib:jdwptransportdt_socket,address127.0.0.1:65267,suspendy,servern -XX:TieredStopAtLevel1 -noverify -Dspring.output.ansi.enabledalways -Dcom.sun.management…

基于以太坊的智能合約開發Solidity(事件日志篇)

//聲明版本號&#xff08;程序中的版本號要和編譯器版本號一致&#xff09; pragma solidity ^0.5.17; //合約 contract EventTest {//狀態變量uint public Variable;//構造函數constructor() public{Variable 100;}event ValueChanged(uint newValue); //事件聲明event Log(…

ElasticSearch之cat plugins API

命令樣例如下&#xff1a; curl -X GET "https://localhost:9200/_cat/plugins?vtrue&pretty" --cacert $ES_HOME/config/certs/http_ca.crt -u "elastic:ohCxPHQBEs5*lo7F9"執行結果輸出如下&#xff1a; name component version…

class064 Dijkstra算法、分層圖最短路【算法】

class064 Dijkstra算法、分層圖最短路【算法】 算法講解064【必備】Dijkstra算法、分層圖最短路 code1 743. 網絡延遲時間 // Dijkstra算法模版&#xff08;Leetcode&#xff09; // 網絡延遲時間 // 有 n 個網絡節點&#xff0c;標記為 1 到 n // 給你一個列表 times&…

法律服務網站建設效果如何

律師事務所及法律知識咨詢機構等往往是眾多人群需求的服務&#xff0c;服務多樣化及內容多元化&#xff0c;市場中也有大量品牌&#xff0c;在實際消費服務中大多以本地事務所為主&#xff0c;而線上咨詢服務則一般沒有區域限制&#xff0c;同行增多及人們知識獲取渠道增加&…

C++-引用和指針區別

文章目錄 1.變量的組成2.指針2.1 定義2.2 使用指針操作變量2.3 為什么使用指針 3.引用3.1 定義3.2 引用注意事項 4.引用和指針的區別 1.變量的組成 變量的組成&#xff1a;變量地址&#xff0c;變量名&#xff0c;變量值 例&#xff1a; int i 12;2.指針 2.1 定義 指針用于存…

如何為游戲角色3D模型設置紋理貼圖

在線工具推薦&#xff1a; 3D數字孿生場景編輯器 - GLTF/GLB材質紋理編輯器 - 3D模型在線轉換 - Three.js AI自動紋理開發包 - YOLO 虛幻合成數據生成器 - 三維模型預覽圖生成器 - 3D模型語義搜索引擎 當談到游戲角色的3D模型風格時&#xff0c;有幾種不同的風格&#xf…

Mybatis中的查詢操作

單表查詢 單表查詢在《初始Mybatis》中已經介紹過&#xff0c;這里就不在介紹了。咱們這里只說單表查詢中的“like查詢”。like查詢單獨使用#{}報錯 <select id"selectByKeyword" resultType"com.example.demo.entity.Userinfo">select * from use…

計網Lesson8 - NAT技術與鏈路層概述

文章目錄 NAT 技術1. 因特網的接入方式2. 公網和私網3. NAT 技術 鏈路層1. 數據鏈路層概述2. 數據鏈路層的三個問題2.1 封裝成幀2.2 透明傳輸2.3 差錯檢測 NAT 技術 1. 因特網的接入方式 光貓將電信號轉換為數字信號發送給路由器 光纖入戶 光纖傳遞的就是數字信號&#xff0c…

python+pytest接口自動化(12)-自動化用例編寫思路 (使用pytest編寫一個測試腳本)

經過之前的學習鋪墊&#xff0c;我們嘗試著利用pytest框架編寫一條接口自動化測試用例&#xff0c;來厘清接口自動化用例編寫的思路。 我們在百度搜索天氣查詢&#xff0c;會出現如下圖所示結果&#xff1a; 接下來&#xff0c;我們以該天氣查詢接口為例&#xff0c;編寫接口測…

錯題總結(三)

1.寫代碼將三個整數數按從大到小輸出。 例如&#xff1a; 輸入&#xff1a;2 3 1 輸出&#xff1a;3 2 1 int main() {int a 0;int b 0;int c 0;int tep 0;scanf("%d%d%d", &a, &b, &c);if (a < b){tep a;a b;b tep;}if (b < c){tep b…