玉田網(wǎng)站制作深圳百度快速排名優(yōu)化
智能指針
文章目錄
- 智能指針
- 內(nèi)存泄漏
- 智能指針解決內(nèi)存泄漏問(wèn)題
- 智能指針的使用及原理
- RAII
- 智能指針對(duì)象的拷貝問(wèn)題
- C++中的智能指針
- auto_ptr
- unique_ptr
- shared_ptr
- weak_ptr
- 定制包裝器
- C++11和boost中智能指針的關(guān)系
內(nèi)存泄漏
-
什么是內(nèi)存泄漏:內(nèi)存泄漏指因?yàn)槭韬龌蝈e(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi) 存泄漏并不是指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,因?yàn)樵O(shè)計(jì)錯(cuò)誤,失去了對(duì) 該段內(nèi)存的控制,因而造成了內(nèi)存的浪費(fèi)。
-
內(nèi)存泄漏的危害:長(zhǎng)期運(yùn)行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺(tái)服務(wù)等等,出現(xiàn) 內(nèi)存泄漏會(huì)導(dǎo)致響應(yīng)越來(lái)越慢,最終卡死。
C/C++程序中一般我們關(guān)心兩種方面的內(nèi)存泄漏:
- 堆內(nèi)存泄漏(Heap leak)
堆內(nèi)存指的是程序執(zhí)行中依據(jù)須要分配通過(guò)malloc / calloc / realloc / new等從堆中分配的一 塊內(nèi)存,用完后必須通過(guò)調(diào)用相應(yīng)的 free或者delete 刪掉。假設(shè)程序的設(shè)計(jì)錯(cuò)誤導(dǎo)致這部分 內(nèi)存沒(méi)有被釋放,那么以后這部分空間將無(wú)法再被使用,就會(huì)產(chǎn)生Heap Leak。
- 系統(tǒng)資源泄漏
指程序使用系統(tǒng)分配的資源,比方套接字、文件描述符、管道等沒(méi)有使用對(duì)應(yīng)的函數(shù)釋放 掉,導(dǎo)致系統(tǒng)資源的浪費(fèi),嚴(yán)重可導(dǎo)致系統(tǒng)效能減少,系統(tǒng)執(zhí)行不穩(wěn)定。
內(nèi)存泄漏很常見(jiàn),解決方案通常分為兩種:
- 事前預(yù)防型。如智能指針等。
- 事后查錯(cuò)型。如泄 漏檢測(cè)工具。
智能指針解決內(nèi)存泄漏問(wèn)題
場(chǎng)景:main函數(shù)里調(diào)用func函數(shù),在func函數(shù)里申請(qǐng)了一個(gè)int大小的空間,然后調(diào)用div函數(shù),在div函數(shù)進(jìn)行除法操作,若出現(xiàn)除零,則直接拋異常,直接跳轉(zhuǎn)到main函數(shù)捕獲異常,從而沒(méi)有釋放掉在func函數(shù)內(nèi)申請(qǐng)的資源,即內(nèi)存泄漏。
#include<iostream>
#include<vector>
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0錯(cuò)誤");return a / b;
}
void func()
{int* ptr = new int;//new了一個(gè)int大小的資源cout << div() << endl;delete ptr;//因?yàn)閽伄惓?dǎo)致這個(gè)int資源沒(méi)有被正常釋放cout << "delete ptr" << endl;
}
int main()
{try{func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
解決方法一:在func函數(shù)內(nèi)捕獲一次異常,進(jìn)行對(duì)申請(qǐng)資源的釋放后,再將異常拋出,讓外層棧幀去捕獲解決異常。
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0錯(cuò)誤");return a / b;
}
void func()
{ int* ptr = new int;//new了一個(gè)int大小的資源
try {cout << div() << endl;
}
catch (...)
{delete ptr;//因?yàn)閽伄惓?dǎo)致這個(gè)int資源沒(méi)有被正常釋放cout << "delete ptr" << endl;throw;
}}
int main()
{try{func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
解決方案二:讓智能指針對(duì)資源進(jìn)行管理,調(diào)用智能指針的構(gòu)造函數(shù)申請(qǐng)資源,調(diào)用智能指針的析構(gòu)函數(shù)釋放資源。
template<class T>
class smartptr
{
public:smartptr(T* ptr=nullptr) :_ptr(ptr){}~smartptr(){if (_ptr){cout << "delete _ptr" << endl;delete _ptr;}}// 像指針一樣T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0錯(cuò)誤");return a / b;
}
void func()
{ smartptr<int> sma(new int);cout << div() << endl;
}
int main()
{try{func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
- 在構(gòu)造sma對(duì)象時(shí),讓smartptr的構(gòu)造函數(shù)去申請(qǐng)資源。
- 當(dāng)退出func函數(shù)這層棧幀時(shí),sma對(duì)象的生命周期結(jié)束,自動(dòng)調(diào)用smartptr的析構(gòu)函數(shù)去釋放資源。
- 將申請(qǐng)到的資源交給一個(gè)smartptr對(duì)象進(jìn)行管理,這樣無(wú)論是否出現(xiàn)除零都能正常釋放資源。
- 此外為了讓smartptr對(duì)象能夠像原生指針一樣使用,還需要對(duì)
*
和->
運(yùn)算符進(jìn)行重載。
智能指針的使用及原理
RAII
RAII(Resource Acquisition Is Initialization)是一種利用對(duì)象生命周期來(lái)控制程序資源(如內(nèi) 存、文件句柄、網(wǎng)絡(luò)連接、互斥量等等)的簡(jiǎn)單技術(shù)。
在對(duì)象構(gòu)造時(shí)獲取資源,接著控制對(duì)資源的訪問(wèn)使之在對(duì)象的生命周期內(nèi)始終保持有效,最后在對(duì)象析構(gòu)的時(shí)候釋放資源。借此,我們實(shí)際上把管理一份資源的責(zé)任托管給了一個(gè)對(duì)象。這種做 法有兩大好處:
- 不需要顯式地釋放資源。
- 采用這種方式,對(duì)象所需的資源在其生命期內(nèi)始終保持有效。
需要注意的是:
- 在對(duì)象構(gòu)造時(shí)獲取資源,在對(duì)象析構(gòu)時(shí)釋放資源,即要具備RAII特性。
- 對(duì)
*
和->
運(yùn)算符進(jìn)行重載,使得對(duì)象具有像指針一樣的行為。 - 智能指針對(duì)象的拷貝問(wèn)題。
智能指針對(duì)象的拷貝問(wèn)題
int main()
{smartptr<int> sm1(new int);smartptr<int> sm2(sm1);//拷貝構(gòu)造smartptr<int> sm3(new int);//賦值重載sm3 = sm1;return 0;
}
- 編譯器默認(rèn)生成的拷貝構(gòu)造是對(duì)內(nèi)置類(lèi)型完成值拷貝(淺拷貝),因此sm2對(duì)象對(duì)sm1對(duì)象的拷貝構(gòu)造,是對(duì)sm1對(duì)象的資源的地址拷貝過(guò)來(lái),即sm1和sm2對(duì)同一份資源進(jìn)行管理,當(dāng)退出棧幀對(duì)象的生命周期結(jié)束時(shí),sm1和sm2一起會(huì)對(duì)一份資源析構(gòu)兩次,造成越界問(wèn)題。
- 編譯器默認(rèn)生產(chǎn)的拷貝賦值函數(shù)是對(duì)內(nèi)置類(lèi)型完成值拷貝(淺拷貝)。因此將sm1對(duì)象賦值給sm3對(duì)象,是將sm1管理資源的地址賦值給sm3的_ptr,即sm1和sm3對(duì)同一份資源進(jìn)行管理,當(dāng)退出棧幀對(duì)象的生命周期結(jié)束時(shí),sm1和sm3一起會(huì)對(duì)同一份資源析構(gòu)兩次,造成越界問(wèn)題。
智能指針要模擬出原生指針的行為,而我們將一個(gè)指針賦值給另一個(gè)指針,其目的就是讓兩個(gè)指針對(duì)同一份資源進(jìn)行管理,但單純的淺拷貝會(huì)導(dǎo)致空間多次釋放,因此根據(jù)實(shí)現(xiàn)的場(chǎng)景不同,衍生出不同的智能指針。
C++中的智能指針
auto_ptr
auto_ptr的實(shí)現(xiàn)目的:對(duì)資源的管理權(quán)進(jìn)行轉(zhuǎn)移
auto_ptr是C++98中引入的智能指針,auto_ptr通過(guò)管理權(quán)轉(zhuǎn)移的方式解決智能指針的拷貝問(wèn)題,保證一個(gè)資源在任何時(shí)刻都只有一個(gè)對(duì)象在對(duì)其進(jìn)行管理,這時(shí)同一個(gè)資源就不會(huì)被多次釋放了。
auto_ptr的模擬實(shí)現(xiàn)
template<class T>
class Auto_ptr
{
public:Auto_ptr(T* ptr=nullptr):_ptr(ptr){}~Auto_ptr() { cout << "delete _ptr" << endl; delete _ptr; }T* operator->(){return _ptr;}T& operator*(){return *_ptr;}Auto_ptr(Auto_ptr<T>& tp)//拷貝構(gòu)造:_ptr(tp._ptr){tp._ptr = nullptr;//懸空}Auto_ptr& operator=(Auto_ptr<T>& tp){if (this!=&tp)//判斷是否是自己賦值給自己{if (_ptr){delete _ptr;_ptr = nullptr;}_ptr = tp._ptr;//轉(zhuǎn)移資源管理權(quán)tp._ptr = nullptr;//懸空}return *this;}
private:T* _ptr;
};
簡(jiǎn)易auto_ptr實(shí)現(xiàn)思路:
- 在構(gòu)造函數(shù)獲取資源,在析構(gòu)函數(shù)釋放資源,利用對(duì)象的生命周期管理資源。
- 對(duì)
*
和->
運(yùn)算符進(jìn)行重載,使得對(duì)象具有像指針一樣的行為。 - 在拷貝構(gòu)造函數(shù)中,將傳入對(duì)象的資源來(lái)構(gòu)造當(dāng)前對(duì)象,然后將傳入對(duì)象管理資源的指針置空。
- 在拷貝賦值函數(shù)中,,先將當(dāng)前對(duì)象的資源釋放,然后將傳入對(duì)象的資源來(lái)構(gòu)造當(dāng)前對(duì)象,最后將傳入對(duì)象管理資源的指針置空。
測(cè)試代碼
int main()
{Auto_ptr<int> apt1(new int(1));*apt1 = 10;Auto_ptr<int> apt2(apt1);Auto_ptr<int> apt3(new int(3));apt3 = apt2;return 0;
}
unique_ptr
unique_ptr的實(shí)現(xiàn)目的:防止拷貝
通過(guò)防止在智能指針之間互相拷貝,暴力的解決了多個(gè)智能指針對(duì)同一塊資源進(jìn)行釋放的問(wèn)題。
template<class T>
class Unique_ptr
{
public:Unique_ptr(T* ptr):_ptr(ptr){}~Unique_ptr(){if (_ptr){cout << "delete _ptr" << endl;delete _ptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}Unique_ptr<T>& operator=(const Unique_ptr<T>& upt) = delete;//禁用拷貝構(gòu)造Unique_ptr(const Unique_ptr<T>& upt) = delete;//禁用拷貝賦值
private:T* _ptr;
};int main()
{Unique_ptr<int> upt1(new int(1));cout << *(upt1) << endl;Unique_ptr<int> upt2(upt1);Unique_ptr<int> upt3(new int(3));upt3 = upt1;return 0;
}
簡(jiǎn)易u(yù)nique_ptr實(shí)現(xiàn)思路:
- 在構(gòu)造函數(shù)獲取資源,在析構(gòu)函數(shù)釋放資源,利用對(duì)象的生命周期管理資源。
- 對(duì)
*
和->
運(yùn)算符進(jìn)行重載,使得對(duì)象具有像指針一樣的行為。 - 用C++98的方式將拷貝構(gòu)造函數(shù)和拷貝賦值函數(shù)聲明為私有,或者用C++11的方式在這兩個(gè)函數(shù)后面加上
=delete
,防止外部調(diào)用。
shared_ptr
share_ptr的實(shí)現(xiàn)目的:是通過(guò)引用計(jì)數(shù)的方式來(lái)實(shí)現(xiàn)多個(gè)shared_ptr對(duì)象之間共享資源。
namespace s
{template<class T>class Share_ptr{public:Share_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)) {}Share_ptr<T>& operator=(const Share_ptr<T>& spt)//賦值重載{if (_ptr != spt._ptr){Release();_ptr = spt._ptr;_pcount = spt._pcount;Addpcount();}return *this;}int use_count(){return *_pcount;}void Addpcount(){(*_pcount)++;}Share_ptr(const Share_ptr<T>& spt)//拷貝構(gòu)造:_ptr(spt._ptr), _pcount(spt._pcount){Addpcount();}void Release(){if (--(*_pcount) == 0 && _ptr){//計(jì)數(shù)為0,釋放資源cout << "delete _ptr" << endl;delete _ptr;delete _pcount;}}~Share_ptr(){Release();}T* operator->(){return _ptr;}T* get(){return _ptr;}T& operator*(){return *_ptr;}private:T* _ptr;int* _pcount;};
}int main()
{s::Share_ptr<int> spt1(new int(1));cout << "spt1 pcount: " << spt1.use_count() << endl;s::Share_ptr<int> spt2(spt1);cout << "spt2 pcount: " << spt2.use_count() << endl;s::Share_ptr<int> spt3(new int(3));spt3 = spt1;cout << "spt3 pcount: " << spt3.use_count() << endl;return 0;
}
?
簡(jiǎn)易auto_ptr實(shí)現(xiàn)思路:
- Shared_ptr在其內(nèi)部,給每個(gè)資源都維護(hù)了著一份計(jì)數(shù)_pcount,用來(lái)記錄該份資源被幾個(gè)對(duì)象共 享。
- 在構(gòu)造函數(shù)中獲取資源,并且設(shè)置計(jì)數(shù)器_pcount為1,表示當(dāng)前只有自己在管理該資源。
- 在拷貝構(gòu)造函數(shù)中,要與傳入的對(duì)象一起管理它所管理的資源,因此同時(shí)將該資源對(duì)應(yīng)的計(jì)數(shù)器
++
。 - 在拷貝賦值函數(shù)中,若在管理資源需要先放棄管理當(dāng)前資源,即先
--
_pcount,然后與傳入對(duì)象一起管理傳入對(duì)象所管理的資源,即++
_pcount。 - 在對(duì)象被銷(xiāo)毀時(shí)(也就是析構(gòu)函數(shù)調(diào)用),就說(shuō)明自己不使用該資源了,對(duì)象的引用計(jì)數(shù)減 一。如果引用計(jì)數(shù)為0,說(shuō)明自己是最后一個(gè)使用該資源的對(duì)象,這時(shí)候必須由自己釋放資源;如果引用計(jì)數(shù)不為0,說(shuō)明還有其他對(duì)象使用該資源,不能釋放資源,否則其他對(duì)象就成野指針了
- 對(duì)
*
和->
運(yùn)算符進(jìn)行重載,使得對(duì)象具有像指針一樣的行為。
為什么share_ptr的引用計(jì)數(shù)要放在堆上?
- 該引用計(jì)數(shù)不能是內(nèi)置函數(shù)成員int類(lèi)型的數(shù)據(jù),這樣會(huì)導(dǎo)致每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù),而當(dāng)多個(gè)對(duì)象管理同一個(gè)資源時(shí),這些對(duì)象所用的引用計(jì)數(shù)應(yīng)該是同一個(gè)。
- 該引用計(jì)數(shù)也不能是靜態(tài)變量,靜態(tài)變量會(huì)造成所有share_ptr類(lèi)型的對(duì)象所共用同一個(gè)引用計(jì)數(shù),那么當(dāng)管理不同資源時(shí)就出計(jì)數(shù)問(wèn)題。
- 因此將shared_ptr的引用計(jì)數(shù)設(shè)置為一個(gè)指針,指針指向一個(gè)計(jì)數(shù)器,當(dāng)一個(gè)資源第一次被管理時(shí)就去堆區(qū)開(kāi)辟一塊空間用于存儲(chǔ)其對(duì)應(yīng)的引用計(jì)數(shù),而當(dāng)前其他對(duì)象也管理該資源時(shí),除了將該資源交給對(duì)象外,該指針也要交給它,其他對(duì)象就能拿到該引用計(jì)數(shù)。
- 這時(shí)候管理同一個(gè)資源的多個(gè)對(duì)象所訪問(wèn)到的引用計(jì)數(shù)就是同一個(gè),管理不同資源的對(duì)象所訪問(wèn)的引用計(jì)數(shù)不是同一個(gè),等同于資源與其引用計(jì)數(shù)一一對(duì)應(yīng)起來(lái)。
std::shared_ptr的線程安全問(wèn)題
- 智能指針對(duì)象中引用計(jì)數(shù)是多個(gè)智能指針對(duì)象共享的,兩個(gè)線程中智能指針的引用計(jì)數(shù)同時(shí)++或–,這個(gè)操作不是原子的,引用計(jì)數(shù)原來(lái)是1,++了兩次,可能還是2。這樣引用計(jì)數(shù)就錯(cuò)亂了。會(huì)導(dǎo)致資源未釋放或者程序崩潰的問(wèn)題。所以只能指針中引用計(jì)數(shù)++、–是需要加鎖的,也就是說(shuō)引用計(jì)數(shù)的操作是線程安全的。
- . 智能指針管理的對(duì)象存放在堆上,兩個(gè)線程中同時(shí)去訪問(wèn),會(huì)導(dǎo)致線程安全問(wèn)題。
首先驗(yàn)證C++庫(kù)里的shared_ptr
#include<iostream>
#include<vector>
#include<memory>
#include<mutex>
#include<thread>
using namespace std;struct Date
{
public:int _year = 0;int _month = 0;int _day = 0;
};
void test_shared_ptr1()
{int n = 50000;mutex mtx;std::shared_ptr<Date> sp1(new Date);thread t1([&](){for (int i = 0; i < n; ++i){std::shared_ptr<Date> sp2(sp1);//mtx.lock();sp2->_year++;sp2->_day++;sp2->_month++;// mtx.unlock();}});thread t2([&](){for (int i = 0; i < n; ++i){std::shared_ptr<Date> sp3(sp1);// mtx.lock();sp3->_year++;sp3->_day++;sp3->_month++;// mtx.unlock();}});t1.join();t2.join();cout << sp1.use_count() << endl;cout << sp1.get() << endl;cout << sp1->_year << endl;cout << sp1->_month << endl;cout << sp1->_day << endl;
}int main()
{test_shared_ptr1();
}
- 首先創(chuàng)建一個(gè)sp1對(duì)象,對(duì)象管理Date結(jié)構(gòu)體,結(jié)構(gòu)體里有三個(gè)成員變量,_year、 _month、 _day。
- 在線程thread1里sp2對(duì)象對(duì)sp1對(duì)象進(jìn)行拷貝構(gòu)造,即sp2也同sp1一起管理同一個(gè)Date結(jié)構(gòu)體,并且對(duì)里面的成員變量進(jìn)行加加,循環(huán)500次;
- 在線程thread2里sp3對(duì)象對(duì)sp1對(duì)象進(jìn)行拷貝構(gòu)造,即sp3也同sp1、sp2一起管理同一個(gè)Date結(jié)構(gòu)體,并且對(duì)里面的成員變量進(jìn)行加加,循環(huán)500次;
- 即sp2和sp3兩個(gè)對(duì)象總和對(duì)Date對(duì)象里的成員變量加加1000次,遍歷完后可以看到三個(gè)參數(shù)都為1000(如下圖)
- 經(jīng)過(guò)多次實(shí)驗(yàn),可以看到存在成員對(duì)象不為1000的結(jié)果,說(shuō)明了shared_ptr存在線程安全問(wèn)題。其根本在于多線程訪問(wèn)同一塊資源時(shí),可能存在多個(gè)線程同時(shí)對(duì)同一個(gè)成員變量進(jìn)行自增或自減操作,而其++操作不具備原子性。因此需要借助鎖,將++操作的代碼劃分為臨界區(qū),互斥鎖將臨界區(qū)保護(hù)起來(lái),每次只允許一個(gè)線程進(jìn)入臨界區(qū),訪問(wèn)臨界資源。
struct Date
{
public:int _year = 0;int _month = 0;int _day = 0;
};void test_shared_ptr1()
{int n = 500;mutex mtx;std::shared_ptr<Date> sp1(new Date);thread t1([&](){for (int i = 0; i < n; ++i){std::shared_ptr<Date> sp2(sp1);mtx.lock();sp2->_year++;sp2->_day++;sp2->_month++;mtx.unlock();}});thread t2([&](){for (int i = 0; i < n; ++i){std::shared_ptr<Date> sp3(sp1);mtx.lock();sp3->_year++;sp3->_day++;sp3->_month++;mtx.unlock();}});t1.join();t2.join();cout << sp1.use_count() << endl;cout << sp1.get() << endl;cout <<"sp1->_year: " << sp1->_year << endl;cout <<"sp1->_month: " << sp1->_month << endl;cout <<"sp1->_year: " << sp1->_day << endl;
}int main()
{test_shared_ptr1();
}
加互斥鎖解決shared_ptr的線程安全問(wèn)題
要解決引用計(jì)數(shù)的線程安全問(wèn)題,本質(zhì)就是讓對(duì)引用計(jì)數(shù)的自增和自減變成一個(gè)原子操作,因此對(duì)引用計(jì)數(shù)的操作進(jìn)行加鎖保護(hù),將對(duì)引用計(jì)數(shù)的操作劃分為臨界區(qū),每次只允許一個(gè)線程進(jìn)入臨界區(qū)做引用計(jì)數(shù)操作。
namespace s
{template<class T>class Share_ptr{public:Share_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)),_mut(new mutex) {}Share_ptr<T>& operator=(const Share_ptr<T>& spt)//賦值重載{if (_ptr != spt._ptr){Release();_ptr = spt._ptr;_pcount = spt._pcount;_mut = spt._mut;Addpcount();}return *this;}int use_count(){return *_pcount;}void Addpcount(){_mut->lock();(*_pcount)++;_mut->unlock();}Share_ptr(const Share_ptr<T>& spt)//拷貝構(gòu)造:_ptr(spt._ptr), _pcount(spt._pcount),_mut(spt._mut){Addpcount();}void Release(){bool flag = false;_mut->lock();if (--(*_pcount) == 0 && _ptr){//計(jì)數(shù)為0,釋放資源cout << "delete _ptr" << endl;delete _ptr;delete _pcount;flag = true;}_mut->unlock();if (flag == true){delete _mut;}}~Share_ptr(){Release();}T* operator->(){return _ptr;}T* get(){return _ptr;}T& operator*(){return *_ptr;}private:T* _ptr;int* _pcount;mutex* _mut;};
}
- 在shared_ptr類(lèi)中新增互斥鎖,為了讓管理同一份資源的多個(gè)對(duì)象訪問(wèn)到的是同一個(gè)互斥鎖,管理不同資源的對(duì)象訪問(wèn)到的不是同一個(gè)互斥鎖,互斥鎖需要在堆區(qū)創(chuàng)建。
- 在拷貝構(gòu)造函數(shù)和拷貝賦值函數(shù)中,除了將對(duì)應(yīng)資源和引用計(jì)數(shù)交給當(dāng)前對(duì)象時(shí),還需要將其資源對(duì)應(yīng)互斥鎖也交給當(dāng)前對(duì)象。
- 當(dāng)一個(gè)資源對(duì)應(yīng)的引用計(jì)數(shù)為0時(shí),,除了將對(duì)應(yīng)資源和引用計(jì)數(shù)釋放外,還需要將其資源對(duì)應(yīng)的互斥鎖釋放。
- 在拷貝構(gòu)造函數(shù)、拷貝賦值函數(shù)、析構(gòu)函數(shù)中需要對(duì)引用計(jì)數(shù)做自增或自減操作,可以將其操作封裝為Addpcount函數(shù)、Release函數(shù),然后只需要在這兩個(gè)函數(shù)中進(jìn)行加鎖保護(hù)。
- shared_ptr只需要保證引用計(jì)數(shù)的線程安全問(wèn)題,而管理資源的線程安全問(wèn)題由管理這塊資源的操作者來(lái)保證
shared_ptr的循環(huán)引用問(wèn)題
struct ListNode
{int _data=0;s::Share_ptr<ListNode> _prev;s::Share_ptr<ListNode> _next;~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{s::Share_ptr<ListNode> node1(new ListNode);s::Share_ptr<ListNode> node2(new ListNode);cout << node1.use_count() << endl;cout << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;cout << node1.use_count() << endl;cout << node2.use_count() << endl;return 0;
}
- 新建一個(gè)listNode對(duì)象,讓shared_ptr類(lèi)node1對(duì)象管理,此時(shí)node1對(duì)應(yīng)的引用計(jì)數(shù)為1,即listNode資源被一個(gè)shared_ptr對(duì)象管理。
- 新建一個(gè)listNode對(duì)象,讓shared_ptr類(lèi)node2對(duì)象管理,此時(shí)node2對(duì)應(yīng)的引用計(jì)數(shù)為1,即listNode資源被一個(gè)shared_ptr對(duì)象管理。
- 此時(shí)node1和node2的引用計(jì)數(shù)都為1,我們不需要手動(dòng)釋放資源。在退出棧幀的時(shí)候會(huì)調(diào)用析構(gòu)函數(shù)進(jìn)行資源釋放
- 讓node1的next指向node2,即node1和node2一起來(lái)管理node2所管理的資源,因此node2的use_count等于2。
- 讓node2的prev指針指向node1,即node1和node2一起來(lái)管理node1所管理的資源,因此node1的use_count等于2。
- 在程序結(jié)束時(shí),會(huì)調(diào)用node1和node2的析構(gòu)函數(shù),此時(shí)node1的use_count減減等于1,node2的use_count減減等于1;但node1無(wú)法被釋放,原因在于node2的prev指針指向node1,想要釋放node1需要先釋放node2的prev指針,釋放node2的prev指針的前提是釋放node2;而node2也無(wú)法被釋放,原因在于node1的next指針指向node2,想要釋放node2需要先釋放node1的next指針,釋放node1的next指針的前提是釋放node1。因此造成了循環(huán)引用問(wèn)題。
weak_ptr
weak_ptr的作用為:
- 構(gòu)造出來(lái)的weak_ptr對(duì)象與shared_ptr對(duì)象管理同一份資源,但不會(huì)增加這塊資源對(duì)應(yīng)的引用計(jì)數(shù)。
- weak_ptr支持用shared_ptr對(duì)象來(lái)構(gòu)造weak_ptr對(duì)象
- weak_ptr不是用來(lái)管理資源釋放的,它主要是用來(lái)解決shared_ptr的循環(huán)引用問(wèn)題。
namespace t
{template<class T>
class weak_ptr
{
public:weak_ptr() :_ptr(nullptr) {}weak_ptr<T>& operator=( s::Share_ptr<T>& spt)//賦值重載{_ptr = spt.get();return *this;}weak_ptr(const s::Share_ptr<T>& spt)//拷貝構(gòu)造:_ptr(spt.get()){}T* operator->(){return _ptr;}T& operator*(){return *_ptr;}private:T* _ptr;
};
}
- 提供一個(gè)無(wú)參的構(gòu)造函數(shù)。
- 拷貝構(gòu)造函數(shù)用shared_ptr構(gòu)造weak_ptr,把shared_ptr的指針傳遞給weak_ptr,weak_ptr也能夠管理shared_ptr所管理的對(duì)象。
- 賦值重載函數(shù)用shared_ptr構(gòu)造weak_ptr,把shared_ptr的指針傳遞給weak_ptr,weak_ptr也能夠管理shared_ptr所管理的對(duì)象。
- 對(duì)
*
和->
運(yùn)算符進(jìn)行重載,使得對(duì)象具有像指針一樣的行為。 - shared_ptr提供一個(gè)get函數(shù),用于獲取指針管理資源。
struct ListNode
{int _data = 0;t::weak_ptr<ListNode> _prev;t::weak_ptr<ListNode> _next;~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{s::Share_ptr<ListNode> node1(new ListNode);s::Share_ptr<ListNode> node2(new ListNode);cout << " node1.use_count: " << node1.use_count() << endl;cout << " node2.use_count: " << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;cout << " node1.use_count: " << node1.use_count() << endl;cout << " node2.use_count: " << node2.use_count() << endl;return 0;
}
可以看到此時(shí)資源正常釋放了。
定制包裝器
當(dāng)智能指針的生命周期結(jié)束,所有的智能指針的析構(gòu)函數(shù)都默認(rèn)以
delete
的方式將資源釋放。實(shí)際上這種方式并不好,因?yàn)椴皇撬兄悄苤羔標(biāo)芾淼馁Y源都是以new
的方式創(chuàng)建的。比如智能指針可能管理以new[]
方式創(chuàng)建的資源,也管理的是一個(gè)文件指針。
int main()
{s::Share_ptr<int> sp(new int[10]);s::Share_ptr<int> sp1(fopen("test.cpp", "r"));return 0;
}
- 在生命周期結(jié)束時(shí),對(duì)于以
new[]
方式創(chuàng)建的資源進(jìn)行delete會(huì)造成程序崩潰,因?yàn)橐?code>new[]申請(qǐng)到的資源必須以delete[]
的方式釋放。而以fopen
打開(kāi)的文件指針必須以fclose
的方式進(jìn)行關(guān)閉。
定制刪除器的實(shí)現(xiàn)問(wèn)題
- C++標(biāo)準(zhǔn)庫(kù)中實(shí)現(xiàn)shared_ptr時(shí)是分成了很多個(gè)類(lèi)的,因此C++標(biāo)準(zhǔn)庫(kù)中可以將刪除器的類(lèi)型設(shè)置為構(gòu)造函數(shù)的模板參數(shù),然后將刪除器的類(lèi)型在各個(gè)類(lèi)之間進(jìn)行傳遞。
- 我們是直接用一個(gè)類(lèi)來(lái)實(shí)現(xiàn)share_ptr,因此不能將定制刪除器的類(lèi)型設(shè)置為構(gòu)造函數(shù)的模板參數(shù)。刪除器需要在析構(gòu)函數(shù)的Release函數(shù)中使用,因此需要用一個(gè)成員變量將器刪除器保存下來(lái),而在定義這個(gè)成員變量時(shí)就需要指定刪除器的類(lèi)型,因此就需要給shared_ptr類(lèi)再增加一個(gè)模板參數(shù),在構(gòu)造shared_ptr對(duì)象時(shí)就指定刪除器的類(lèi)型。然后增加一個(gè)支持傳入刪除器的構(gòu)造函數(shù),在構(gòu)造shared_ptr對(duì)象時(shí)就傳遞刪除器,在釋放資源時(shí)就調(diào)用該刪除器即可。設(shè)置一個(gè)默認(rèn)刪除器,當(dāng)用戶定義shared_ptr對(duì)象時(shí)不傳入刪除器,就默認(rèn)以
delete
的方式釋放資源。
template<class T>class default_delete{public:void operator()(T* ptr){cout << "delete ptr" << endl;delete ptr;}};template<class T>class Delarry{public:void operator()( T* arr){cout << "delete[]" << arr << endl;delete[]arr;}};template<class T,class D=default_delete<T>>class Share_ptr{public:Share_ptr(T* ptr ,D del) :_ptr(ptr), _pcount(new int(1)),_mut(new mutex),_del(del) {}void Release(){bool flag = false;_mut->lock();if (--(*_pcount) == 0 && _ptr){//計(jì)數(shù)為0,釋放資源cout << "delete _ptr" << endl;_del( _ptr);//調(diào)用對(duì)應(yīng)制定刪除器釋放資源delete _pcount;flag = true;}_mut->unlock();if (flag == true){delete _mut;}}~Share_ptr(){Release();}//......private:T* _ptr;int* _pcount;mutex* _mut;D _del;
class Fclose
{
public:void operator()(FILE* ptr){cout << "fclose ptr" << endl;fclose(ptr);}
};int main()
{s::Share_ptr<int,s::Delarry<int>> sp(new int[10],s::Delarry<int>());s::Share_ptr<FILE, function<void(FILE*)>> sp2(fopen("test.cpp", "r"), [](FILE* ptr) {cout << "fclose: " << ptr << endl;fclose(ptr); });return 0;
}
C++11和boost中智能指針的關(guān)系
- C++ 98 中產(chǎn)生了第一個(gè)智能指針auto_ptr。
- . C++ boost給出了更實(shí)用的scoped_ptr和shared_ptr和weak_ptr。
- . C++ TR1,引入了shared_ptr等。不過(guò)注意的是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)的。