C++手?jǐn)]智能指針的教程分享
前言
大家好,今天是【重學(xué)C++】的第三講,書(shū)接上回,第二講《02 脫離指針陷阱:深入淺出 C++ 智能指針》介紹了C++智能指針的一些使用方法和基本原理。今天,我們自己動(dòng)手,從0到1實(shí)現(xiàn)一下自己的unique_ptr
和shared_ptr
。
回顧
智能指針的基本原理是基于RAII設(shè)計(jì)理論,自動(dòng)回收內(nèi)存資源,從根本上避免內(nèi)存泄漏。在第一講《01 C++ 如何進(jìn)行內(nèi)存資源管理?》介紹RAII的時(shí)候,就已經(jīng)給了一個(gè)用于封裝int
類型指針,實(shí)現(xiàn)自動(dòng)回收資源的代碼實(shí)例:
class?AutoIntPtr?{ public: ????AutoIntPtr(int*?p?=?nullptr)?:?ptr(p)?{} ????~AutoIntPtr()?{?delete?ptr;?} ????int&?operator*()?const?{?return?*ptr;?} ????int*?operator->()?const?{?return?ptr;?} private: ????int*?ptr; };
我們從這個(gè)示例出發(fā),一步步完善我們自己的智能指針。
模版化
這個(gè)類有個(gè)明顯的問(wèn)題:只能適用于int類指針。所以我們第一步要做的,就是把它改造成一個(gè)類模版,讓這個(gè)類適用于任何類型的指針資源。code show time
template?<typename?T> class?smart_ptr?{ public: ?explicit?smart_ptr(T*?ptr?=?nullptr):?ptr_(ptr)?{} ?~smart_ptr()?{ ??delete?ptr_; ?} ?T&?operator*()?const?{?return?*ptr_;?} ?T*?operator->()?const?{?return?ptr_;?} private: ?T*?ptr_; }
我給我們的智能指針類用了一個(gè)更抽象,更切合的類名:smart_ptr
。
和AutoIntPtr
相比,我們把smart_ptr
設(shè)計(jì)成一個(gè)類模版,原來(lái)代碼中的int
改成模版參數(shù)T
,非常簡(jiǎn)單。使用時(shí)也只要把AutoIntPtr(new int(9))
改成smart_ptr<int>(new int(9))
即可。
另外,有一點(diǎn)值得注意,smart_ptr
的構(gòu)造函數(shù)使用了explicit
, explicit
關(guān)鍵字主要用于防止隱式的類型轉(zhuǎn)換。代碼中,如果原生指針隱式地轉(zhuǎn)換為智能指針類型可能會(huì)導(dǎo)致一些潛在的問(wèn)題。至于會(huì)有什么問(wèn)題,你那聰明的小腦瓜看完下面的代碼肯定能理解了:
void?foo(smart_ptr<int>?int_ptr)?{ ????//?... } int?main()?{ ????int*?raw_ptr?=?new?int(42); ????foo(raw_ptr);??//?隱式轉(zhuǎn)換為?smart_ptr<int> ????std::cout?<<?*raw_ptr?<<?std::endl;???//?error:?raw_ptr已經(jīng)被回收了 ????//?... }
假設(shè)我們沒(méi)有為smart_ptr
構(gòu)造函數(shù)加上explicit
,原生指針raw_ptr
在傳給foo
函數(shù)后,會(huì)被隱形轉(zhuǎn)換為smart_ptr<int>
, foo
函數(shù)調(diào)用結(jié)束后,棲構(gòu)入?yún)⒌?code>smart_ptr<int>時(shí)會(huì)把raw_ptr
給回收掉了,所以后續(xù)對(duì)raw_ptr
的調(diào)用都會(huì)失敗。
拷貝還是移動(dòng)
當(dāng)前我們沒(méi)有為smart_ptr
自定義拷貝構(gòu)造函數(shù)/移動(dòng)構(gòu)造函數(shù),C++會(huì)為smart_ptr
生成默認(rèn)的拷貝/移動(dòng)構(gòu)造函數(shù)。默認(rèn)的拷貝/移動(dòng)構(gòu)造函數(shù)邏輯很簡(jiǎn)單:把每個(gè)成員變量拷貝/移動(dòng)到目標(biāo)對(duì)象中。
按當(dāng)前smart_ptr
的實(shí)現(xiàn),我們假設(shè)有以下代碼:
smart_ptr<int>?ptr1{new?int(10)}; smart_ptr<int>?ptr2?=?ptr1;
這段代碼在編譯時(shí)不會(huì)出錯(cuò),問(wèn)題在運(yùn)行時(shí)才會(huì)暴露出來(lái):第二行將ptr1
管理的指針復(fù)制給了ptr2
,所以會(huì)重復(fù)釋放內(nèi)存,導(dǎo)致程序奔潰。
為了避免同一塊內(nèi)存被重復(fù)釋放。解決辦法也很簡(jiǎn)單:
- 獨(dú)占資源所有權(quán),每時(shí)每刻一個(gè)內(nèi)存對(duì)象(資源)只能有一個(gè)
smart_ptr
占有它。 - 一個(gè)內(nèi)存對(duì)象(資源)只有在最后一個(gè)擁有它的
smart_ptr
析構(gòu)時(shí)才會(huì)進(jìn)行資源回收。
獨(dú)占所有權(quán) - unique_smart_ptr
獨(dú)占資源的所有權(quán),并不是指禁用掉smart_ptr
的拷貝/移動(dòng)函數(shù)(當(dāng)然這也是一種簡(jiǎn)單的避免重復(fù)釋放內(nèi)存的方法)。而是smart_ptr
在拷貝時(shí),代表資源對(duì)象的指針不是復(fù)制到另外一個(gè)smart_ptr
,而是"移動(dòng)"到新smart_ptr
。移動(dòng)后,原來(lái)的smart_ptr.ptr_
== nullptr, 這樣就完成了資源所有權(quán)的轉(zhuǎn)移。這也是C++ unique_ptr
的基本行為。我們?cè)谶@里先把它命名為unique_smart_ptr
,代碼完整實(shí)現(xiàn)如下:
template?<typename?T> class?unique_smart_ptr?{ public: ?explicit?unique_smart_ptr(T*?ptr?=?nullptr):?ptr_(ptr)?{} ?~unique_smart_ptr()?{ ??delete?ptr_; ?} ?//?1.?自定義移動(dòng)構(gòu)造函數(shù) ?unique_smart_ptr(unique_smart_ptr&&?other)?{ ??//?1.1?把other.ptr_?賦值到this->ptr_ ??ptr_?=?other.ptr_; ??//?1.2?把other.ptr_指為nullptr,other不再擁有資源指針 ??other.ptr_?=?nullptr; ?} ?//?2.?自定義賦值行為 ?unique_smart_ptr&?operator?=?(unique_smart_ptr?rhs)?{ ??//?2.1?交換rhs.ptr_和this->ptr_ ??std::swap(rhs.ptr_,?this->ptr_); ??return?*this; ?} T&?operator*()?const?{?return?*ptr_;?} T*?operator->()?const?{?return?ptr_;?} private: ?T*?ptr_; };
自定義移動(dòng)構(gòu)造函數(shù)。在移動(dòng)構(gòu)造函數(shù)中,我們先是接管了other.ptr_
指向的資源對(duì)象,然后把other
的ptr_
置為nullptr,這樣在other
析構(gòu)時(shí)就不會(huì)錯(cuò)誤釋放資源內(nèi)存。
同時(shí),根據(jù)C++的規(guī)則,手動(dòng)提供移動(dòng)構(gòu)造函數(shù)后,就會(huì)自動(dòng)禁用拷貝構(gòu)造函數(shù)。也就是我們能得到以下效果:
unique_smart_ptr<int>?ptr1{new?int(10)}; unique_smart_ptr<int>?ptr2?=?ptr1;?//?error unique_smart_ptr<int>?ptr3?=?std::move(ptr1);?//?ok unique_smart_ptr<int>?ptr4{ptr1}?//?error unique_smart_ptr<int>?ptr5{std::move(ptr1)}?//?ok
自定義賦值函數(shù)。在賦值函數(shù)中,我們使用std::swap
交換了 rhs.ptr_
和this->ptr_
,注意,這里不能簡(jiǎn)單的將rhs.ptr_
設(shè)置為nullptr,因?yàn)?code>this->ptr_可能有指向一個(gè)堆對(duì)象,該對(duì)象需要轉(zhuǎn)給rhs
,在賦值函數(shù)調(diào)用結(jié)束,rhs
析構(gòu)時(shí)順便釋放掉。避免內(nèi)存泄漏。
注意賦值函數(shù)的入?yún)?code>rhs的類型是unique_smart_ptr
而不是unique_smart_ptr&&
,這樣創(chuàng)建rhs
使用移動(dòng)構(gòu)造函數(shù)還是拷貝構(gòu)造函數(shù)完全取決于unique_smart_ptr
的定義。因?yàn)?code>unique_smart_ptr當(dāng)前只保留了移動(dòng)構(gòu)造函數(shù),所以rhs
是通過(guò)移動(dòng)構(gòu)造函數(shù)創(chuàng)建的。
多個(gè)智能指針共享對(duì)象 - shared_smart_ptr
學(xué)過(guò)第二講的shared_ptr
, 我們知道它是利用計(jì)數(shù)引用的方式,實(shí)現(xiàn)了多個(gè)智能指針共享同一個(gè)對(duì)象。當(dāng)最后一個(gè)持有對(duì)象的智能指針析構(gòu)時(shí),計(jì)數(shù)器減為0,這個(gè)時(shí)候才會(huì)回收資源對(duì)象。
我們先給出shared_smart_ptr
的類定義
template?<typename?T> class?shared_smart_ptr?{ public: ?//?構(gòu)造函數(shù) ?explicit?shared_smart_ptr(T*?ptr?=?nullptr) ?//?析構(gòu)函數(shù) ?~shared_smart_ptr() ?//?移動(dòng)構(gòu)造函數(shù) ?shared_smart_ptr(shared_smart_ptr&&?other) ?//?拷貝構(gòu)造函數(shù) ?shared_smart_ptr(const?shared_smart_ptr&?other) ?//?賦值函數(shù) ?shared_smart_ptr&?operator?=?(shared_smart_ptr?rhs) ?//?返回當(dāng)前引用次數(shù) ?int?use_count()?const?{?return?*count_;?} ?T&?operator*()?const?{?return?*ptr_;?} ?T*?operator->()?const?{?return?ptr_;?} private: ?T*?ptr_; ?int*?count_; }
暫時(shí)不考慮多線程并發(fā)安全的問(wèn)題,我們簡(jiǎn)單在堆上創(chuàng)建一個(gè)int類型的計(jì)數(shù)器count_
。下面詳細(xì)展開(kāi)各個(gè)函數(shù)的實(shí)現(xiàn)。
為了避免對(duì)count_
的重復(fù)刪除,我們保持:只有當(dāng)ptr_ != nullptr
時(shí),才對(duì)count_
進(jìn)行賦值。
構(gòu)造函數(shù)
同樣的,使用explicit
避免隱式轉(zhuǎn)換。除了賦值ptr_
, 還需要在堆上創(chuàng)建一個(gè)計(jì)數(shù)器。
explicit?shared_smart_ptr(T*?ptr?=?nullptr){ ?ptr_?=?ptr; ?if?(ptr_)?{ ??count_?=?new?int(1); ?} }
析構(gòu)函數(shù)
在析構(gòu)函數(shù)中,需要根據(jù)計(jì)數(shù)器的引用數(shù)判斷是否需要回收對(duì)象。
~shared_smart_ptr()?{ ?//?ptr_為nullptr,不需要做任何處理 ?if?(ptr_)?{ ??return; ?} ?//?計(jì)數(shù)器減一 ?--(*count_); ?//?計(jì)數(shù)器減為0,回收對(duì)象 ?if?(*count_?==?0)?{ ??delete?ptr_; ??delete?count_; ??return; ?} }
移動(dòng)構(gòu)造函數(shù)
添加對(duì)count_
的處理
shared_smart_ptr(shared_smart_ptr&&?other)?{ ?ptr_?=?other.ptr_; ?count_?=?other.count_; ?other.ptr_?=?nullptr; ?other.count_?=?nullptr; }
賦值構(gòu)造函數(shù)
添加交換count_
shared_smart_ptr&?operator?=?(shared_smart_ptr?rhs)?{ ?std::swap(rhs.ptr_,?this->ptr_); ?std::swap(rhs.count_,?this->count_); ?return?*this; }
拷貝構(gòu)造函數(shù)
對(duì)于shared_smart_ptr
,我們需要手動(dòng)支持拷貝構(gòu)造函數(shù)。主要處理邏輯是賦值ptr_
和增加計(jì)數(shù)器的引用數(shù)。
shared_smart_ptr(const?shared_smart_ptr&?other)?{ ?ptr_?=?other.ptr_; ?count_?=?other.count_; ?if?(ptr_)?{ ??(*count_)++; ?} }
這樣,我們就實(shí)現(xiàn)了一個(gè)自己的共享智能指針,貼一下完整代碼
template?<typename?T> class?shared_smart_ptr?{ public: ?explicit?shared_smart_ptr(T*?ptr?=?nullptr){ ??ptr_?=?ptr; ??if?(ptr_)?{ ???count_?=?new?int(1); ??} ?} ?~shared_smart_ptr()?{ ??//?ptr_為nullptr,不需要做任何處理 ??if?(ptr_?==?nullptr)?{ ???return; ??} ??//?計(jì)數(shù)器減一 ??--(*count_); ??//?計(jì)數(shù)器減為0,回收對(duì)象 ??if?(*count_?==?0)?{ ???delete?ptr_; ???delete?count_; ??} ?} ?shared_smart_ptr(shared_smart_ptr&&?other)?{ ??ptr_?=?other.ptr_; ??count_?=?other.count_; ??other.ptr_?=?nullptr; ??other.count_?=?nullptr; ?} ?shared_smart_ptr(const?shared_smart_ptr&?other)?{ ??ptr_?=?other.ptr_; ??count_?=?other.count_; ??if?(ptr_)?{ ???(*count_)++; ??} ?} ?shared_smart_ptr&?operator?=?(shared_smart_ptr?rhs)?{ ??std::swap(rhs.ptr_,?this->ptr_); ??std::swap(rhs.count_,?this->count_); ??return?*this; ?} ?int?use_count()?const?{?return?*count_;?}; ?T&?operator*()?const?{?return?*ptr_;?}; ?T*?operator->()?const?{?return?ptr_;?}; private: ?T*?ptr_; ?int*?count_; };
使用下面代碼進(jìn)行驗(yàn)證:
int?main(int?argc,?const?char**?argv)?{ ?shared_smart_ptr<int>?ptr1(new?int(1)); ?std::cout?<<?"[初始化ptr1]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl; ?{ ??//?賦值使用拷貝構(gòu)造函數(shù) ??shared_smart_ptr<int>?ptr2?=?ptr1; ??std::cout?<<?"[使用拷貝構(gòu)造函數(shù)將ptr1賦值給ptr2]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl; ??//?賦值使用移動(dòng)構(gòu)造函數(shù) ??shared_smart_ptr<int>?ptr3?=?std::move(ptr2); ??std::cout?<<?"[使用移動(dòng)構(gòu)造函數(shù)將ptr2賦值給ptr3]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl; ?} ?std::cout?<<?"[ptr2和ptr3析構(gòu)后]?use?count?of?ptr1:?"?<<?ptr1.use_count()?<<?std::endl; }
運(yùn)行結(jié)果:
[初始化ptr1] use count of ptr1: 1
[使用拷貝構(gòu)造函數(shù)將ptr1賦值給ptr2] use count of ptr1: 2
[使用移動(dòng)構(gòu)造函數(shù)將ptr2賦值給ptr3] use count of ptr1: 2
[ptr2和ptr3析構(gòu)后] use count of ptr1: 1
總結(jié)
這一講我們從AutoIntPtr
出發(fā),先是將類進(jìn)行模版化,使其能夠管理任何類型的指針對(duì)象,并給該類起了一個(gè)更抽象、更貼切的名稱——smart_ptr
。
接著圍繞著「如何正確釋放資源對(duì)象指針」的問(wèn)題,一步步手?jǐn)]了兩個(gè)智能指針 ——unique_smart_ptr
和shared_smart_ptr
。相信大家現(xiàn)在對(duì)智能指針有一個(gè)較為深入的理解了。
以上就是C++手?jǐn)]智能指針的教程分享的詳細(xì)內(nèi)容,更多關(guān)于C++智能指針的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解C++中的內(nèi)存同步模式(memory order)
這篇文章主要介紹了C++中的內(nèi)存同步模式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04全面了解#pragma once與 #ifndef的區(qū)別
下面小編就為大家?guī)?lái)一篇全面了解#pragma once與 #ifndef的區(qū)別。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08一些語(yǔ)言的按行讀取文件的代碼實(shí)現(xiàn)小結(jié)
這篇文章主要介紹了一些語(yǔ)言的按行讀取文件的代碼實(shí)現(xiàn)小結(jié),這里羅列了Java和C語(yǔ)言和C++以及PHP的實(shí)現(xiàn)需要的朋友可以參考下2015-08-08C++中關(guān)鍵字const的詳細(xì)說(shuō)明和使用介紹(最全)
const在C/C++中是十分重要的,如果單純理解為“常量”那么你的格局就小了,今天在這里給大家介紹一下const在C++中具體詳細(xì)的用法,需要的朋友可以參考下2025-03-03C++ 中CListCtrl的每個(gè)項(xiàng)都顯示不同的提示信息
這篇文章主要介紹了C++ 中CListCtrl的每個(gè)項(xiàng)都顯示不同的提示信息的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09Matlab實(shí)現(xiàn)二維散點(diǎn)主方向直方圖的繪制詳解
這篇文章主要為大家詳細(xì)介紹了如何利用Matlab實(shí)現(xiàn)二維散點(diǎn)主方向直方圖的繪制,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Matlab有一定幫助,需要的可以參考一下2022-09-09