深入解析C++中的智能指針
一、RAII和智能指針
RAII是Resource Acquisition Is Initialization的縮寫,它的意思是獲取資源立即初始化。本質(zhì)是?種利用對(duì)象生命周期來(lái)管理獲取到的動(dòng)態(tài)資源,避免資源泄漏
智能指針類除了滿足RAII的設(shè)計(jì)思路,還要方便對(duì)資源進(jìn)行訪問,所以智能指針類還會(huì)想迭代器類?樣,重載 operator*/operator->/operator[]等運(yùn)算符,
例:
template<class T>
class smart_ptr
{
public:
smart_ptr(T* ptr)
:_ptr(ptr)
{}
~smart_ptr()
{
cout << "delete[]" << _ptr << endl;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
private:
T* _ptr;
};二、C++標(biāo)準(zhǔn)庫(kù)智能指針的使用
C++標(biāo)準(zhǔn)庫(kù)中的智能指針都在< memory >這個(gè)頭文件下面,因此在使用前需要包含這一頭文件。除了weak_ptr以外,其它都符合RAII和像指針?樣訪問的行為,原因主要是解決智能指針拷貝時(shí)的思路不同
C++標(biāo)準(zhǔn)庫(kù)主要提供了四種智能指針:
auto_ptr: 它是在C++98中設(shè)計(jì)出來(lái)的智能指針,其特點(diǎn)是拷貝時(shí)把被拷貝對(duì)象的資源管理權(quán)轉(zhuǎn)移給拷貝對(duì)象,這個(gè)設(shè)計(jì)是非常糟糕的,因?yàn)樗鼤?huì)讓被拷貝的對(duì)象懸空,當(dāng)我們對(duì)其進(jìn)行訪問時(shí)會(huì)發(fā)生報(bào)錯(cuò)。因此在日常工作中不建議使用這一指針
以下這三種指針都是在C++11中設(shè)計(jì)出來(lái)的
unique_ptr:它的意思是唯一指針,其特點(diǎn)是不支持拷貝只支持移動(dòng),因此在不需要拷貝的場(chǎng)景下就可以使用它
shared_ptr: 它的意思是共享指針,其特點(diǎn)是支持拷貝也支持移動(dòng),因此在需要拷貝的場(chǎng)景下就可以使用它
weak_ptr:它的意思的弱指針,其完全不同于上面的智能指針,因?yàn)樗?strong>不支持RAII,也就意味著不能用它直接管理資源。其作用是用來(lái)解決循環(huán)引用的問題
例:
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1,int month = 1,int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
~Date()
{
cout << "~Date()" << endl;
}
};
int main()
{
auto_ptr<Date> ap1(new Date);
//ap1的管理權(quán)轉(zhuǎn)移給ap2
auto_ptr<Date> ap2(ap1);
//報(bào)錯(cuò):對(duì)空指針進(jìn)行訪問
ap1->_year++;
unique_ptr<Date> up1(new Date);
//報(bào)錯(cuò):不支持拷貝
unique_ptr<Date> up2(up1);
//支持移動(dòng),移動(dòng)后up1也為空,不能對(duì)其進(jìn)行訪問
unique_ptr<Date> up2(move(up1));
shared_ptr<Date> sp1(new Date);
//支持拷貝也支持移動(dòng)
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(move(sp1));
return 0;
}刪除器
智能指針析構(gòu)時(shí)默認(rèn)是進(jìn)行delete釋放資源,這也就意味著如果不是new出來(lái)的資源交給智能指針管理,析構(gòu)時(shí)就會(huì)崩潰。因此智能指針支持在構(gòu)造時(shí)給?個(gè)刪除器,而刪除器本質(zhì)就是?個(gè)可調(diào)用對(duì)象,這個(gè)可調(diào)用對(duì)象中實(shí)現(xiàn)你想要的釋放資源的方式。即當(dāng)我們?cè)跇?gòu)造智能指針時(shí),給了定制的刪除器,智能指針析構(gòu)時(shí)就會(huì)調(diào)用我們?cè)O(shè)計(jì)出的刪除器去釋放資源
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1,int month = 1,int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
~Date()
{
cout << "~Date()" << endl;
}
};
template<class T>
class DeleteArray
{
public:
void operator()(T* ptr)
{
delete[] ptr;
}
};
template<class T>
void DeleteArrayFunc(T* ptr)
{
deletr[] ptr;
};
int main()
{
//這樣去構(gòu)造程序會(huì)報(bào)錯(cuò)
//unique_ptr<Date> up(new Date[10]);
//shared_ptr<Date> sp(new Date[10]);
//解決方案1:因?yàn)閚ew[]經(jīng)常使用,因此unique_ptr和shared_ptr
//實(shí)現(xiàn)了?個(gè)特化版本,這個(gè)特化版本在析構(gòu)時(shí)用的delete[]
unique_ptr<Date[]> up(new Date[10]);
shared_ptr<Date[]> sp(new Date[10]);
//解決方案2:使用仿函數(shù)對(duì)象做刪除器
//這里需要注意:unique_ptr和shared_ptr在支持刪除器的方式是有所不同的
//unique_ptr在類模板參數(shù)支持的,shared_ptr是構(gòu)造函數(shù)參數(shù)支持的
unique_ptr<Date, DeleteArray<Date>> up1(new Date[10]);
shared_ptr<Date> sp1(new Date[10], DeleteArray<Date>());
// 函數(shù)指針做刪除器
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);
// lambda表達(dá)式做刪除器
auto del = [](Date* ptr) {delete[] ptr; };
unique_ptr<Date, decltype(del)> up4(new Date[10], del);
shared_ptr<Date> sp4(new Date[10], del);
return 0;
}使用時(shí)需要注意以下幾點(diǎn):
1.shared_ptr 除了支持用指向資源的指針構(gòu)造,還支持 make_shared 用初始化資源對(duì)象的值直接構(gòu)造。
2.shared_ptr 和 unique_ptr 都支持了operator bool的類型轉(zhuǎn)換,如果智能指針對(duì)象是?個(gè)空對(duì)象沒有管理資源,則返回false,否則返回true,意味著我們可以直接把智能指針對(duì)象給if判斷是否為空。
3.shared_ptr 和 unique_ptr 在構(gòu)造函數(shù)中都得使用explicit來(lái)修飾,防止普通指針隱式類型轉(zhuǎn)換成智能指針對(duì)象。
例:
int main()
{
shared_ptr<Date> sp1(new Date(2025, 10, 9));
shared_ptr<Date> sp2 = make_shared<Date>(2025, 10, 9);
if (sp1)
cout << "sp1 is not nullptr" << endl;
return 0;
}三、智能指針的原理及其模擬實(shí)現(xiàn)
1.auto_ptr
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};2.unique_ptr
template<class T>
class unique_ptr
{
public:
// 加explicit是防止普通指針隱式類型
// 轉(zhuǎn)化為智能指針對(duì)象
explicit unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
//不支持左值的拷貝構(gòu)造
unique_ptr(const unique_ptr<T>& up) = delete;
//不支持左值的賦值
unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
//支持右值的拷貝構(gòu)造
unique_ptr(unique_ptr<T>&& up)
:_ptr(up._ptr)
{
up._ptr = nullptr;
}
//支持右值的賦值
unique_ptr<T>& operator=(unique_ptr<T>&& up)
{
delete _ptr;
_ptr = up._ptr;
up._ptr = nullptr;
}
private:
T* _ptr;
};
}3.重點(diǎn):shared_ptr
shared_ptr的拷貝底層是采用引用計(jì)數(shù)的方式來(lái)實(shí)現(xiàn)的,讓多個(gè)shared_ptr對(duì)象共用同一份資源

template<class T>
class shared_ptr
{
public:
explicit shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1))
{}
//拷貝
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
{
++(*_pcount);
}
~shared_ptr()
{
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
//賦值
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_pcount) == 0)
{
delete _pcount;
delete _ptr;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
}
return *this;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
int* _pcount;
};定制刪除器版本:
template<class T>
class shared_ptr
{
public:
explicit shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
template<class D>
shared_ptr(T* ptr, D del)
:_ptr(ptr)
,_del(del)
,_pcount(new int(1))
{}
void release()
{
if (--(*_pcount) == 0)
{
_del(_ptr);
delete _pcount;
_ptr = nullptr;
_pcount = nullptr;
}
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
,_del(sp._del)
{
++(*_pcount);
}
~shared_ptr()
{
release();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
release();
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
_del = sp._del;
}
return *this;
}
int use_count() const
{
return *_pcount;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
function<void(T*)> _del = [](T* ptr) {delete ptr; };
};??四、shared_ptr和weak_ptr之間的關(guān)系
1.shared_ptr循環(huán)引用的問題
shared_ptr在大多數(shù)情況下管理資源都非常合適,但在循環(huán)引用場(chǎng)景下會(huì)出現(xiàn)內(nèi)存泄漏的問題,因此我們要學(xué)會(huì)使用weak_ptr來(lái)解決這類問題

當(dāng)n1和n2進(jìn)行析構(gòu)時(shí),引用計(jì)數(shù)為1不為0,導(dǎo)致內(nèi)存泄漏

2.weak_ptr的介紹及其模擬實(shí)現(xiàn)
• weak_ptr不支持RAII,也不支持訪問資源,它支持綁定到shared_ptr,綁定到shared_ptr時(shí),不增加shared_ptr的引用計(jì)數(shù),因此就可以解決循環(huán)引用的問題
• weak_ptr也沒有重載operator*和operator->等,因?yàn)樗粎⑴c資源管理,但如果它綁定的shared_ptr已經(jīng)釋放了資源,我們?cè)趯?duì)其進(jìn)行訪問,那是非常危險(xiǎn)的。因此它設(shè)計(jì)出expired幫助我們檢查指向的資源是否過期,使用use_count也可獲取shared_ptr中的引用計(jì)數(shù)
例:

五、C++11和boost智能指針中的關(guān)系
Boost庫(kù)是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的?些C++程序庫(kù)的總稱,C++11及之后的新語(yǔ)法和庫(kù)有很多都是從Boost中來(lái)的
C++98中產(chǎn)生了第?個(gè)智能指針auto_ptr
C++boost庫(kù)也給出了更實(shí)用的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等
C++TR1,引入了shared_ptr等,不過注意的是TR1并不是標(biāo)準(zhǔn)版
C++11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr對(duì)應(yīng)boost的scoped_ptr。并且這些智能指針的實(shí)現(xiàn)原理是參考boost中的實(shí)現(xiàn)的
六、內(nèi)存泄漏
1.什么是內(nèi)存泄漏以及造成的危害
內(nèi)存泄漏指因?yàn)槭韬龌蝈e(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存,?般是忘記釋放或者發(fā)生異常釋放程序未能執(zhí)行導(dǎo)致的。
危害:普通程序在資源少的情況下運(yùn)行?會(huì)就結(jié)束了出現(xiàn)內(nèi)存泄漏問題也不是很大。但在長(zhǎng)期運(yùn)行的程序以及有大量資源的情況下出現(xiàn)內(nèi)存泄漏,影響是非常大的,如操作系統(tǒng)、后臺(tái)服務(wù)、長(zhǎng)時(shí)間運(yùn)行的客戶端等等,不斷出現(xiàn)內(nèi)存泄漏會(huì)導(dǎo)致可用內(nèi)存不斷變少,各種功能響應(yīng)越來(lái)越慢,最終卡死。
2.如何避免內(nèi)存泄漏
1.養(yǎng)成良好的編碼規(guī)范,申請(qǐng)的內(nèi)存空間記著匹配的去釋放
2.盡量使用智能指針來(lái)管理資源,如果在某些場(chǎng)景比較特殊的情況下,采用RAII思想自己造個(gè)輪子來(lái)管理
3.定期使用內(nèi)存泄露工具
到此這篇關(guān)于C++智能指針介紹的文章就介紹到這了,更多相關(guān)C++智能指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
鏈接庫(kù)動(dòng)態(tài)鏈接庫(kù)詳細(xì)介紹
靜態(tài)鏈接庫(kù).lib和動(dòng)態(tài)鏈接庫(kù).dll。其中動(dòng)態(tài)鏈接庫(kù)在被使用的時(shí)候,通常還提供一個(gè).lib,稱為引入庫(kù),它主要提供被Dll導(dǎo)出的函數(shù)和符號(hào)名稱,使得鏈接的時(shí)候能夠找到dll中對(duì)應(yīng)的函數(shù)映射2012-11-11
C++ 構(gòu)造函數(shù)和析構(gòu)函數(shù)示例詳解
本文詳細(xì)介紹了C++中的構(gòu)造函數(shù)和析構(gòu)函數(shù),包括它們的特點(diǎn)、調(diào)用時(shí)機(jī)以及在資源管理中的重要性,同時(shí),通過與Python的對(duì)比,展示了如何在Python中模擬C++的構(gòu)造函數(shù)特性,并強(qiáng)調(diào)了兩種語(yǔ)言在內(nèi)存管理和資源管理機(jī)制上的差異,感興趣的朋友跟隨小編一起看看吧2025-11-11
基于OpenCV讀取攝像頭實(shí)現(xiàn)單個(gè)人臉驗(yàn)證MFC程序
這篇文章主要為大家詳細(xì)介紹了基于OpenCV讀取攝像頭實(shí)現(xiàn)單個(gè)人臉驗(yàn)證MFC程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
C語(yǔ)言浮點(diǎn)型數(shù)據(jù)在內(nèi)存中的存儲(chǔ)方式詳解
任何數(shù)據(jù)在內(nèi)存中都是以二進(jìn)制的形式存儲(chǔ)的,例如一個(gè)short型數(shù)據(jù)1156,其二進(jìn)制表示形式為00000100 10000100,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言浮點(diǎn)型數(shù)據(jù)在內(nèi)存中的存儲(chǔ)方式,需要的朋友可以參考下2023-03-03

