沙田鎮(zhèn)做網(wǎng)站湖北seo診斷
智能指針
- 為什么使用智能指針?
- 概念
- 分類
- auto_ptr
- unique_ptr
- shared_ptr
- 循環(huán)引用
- weak_ptr
為什么使用智能指針?
考慮以下場(chǎng)景:
void div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0錯(cuò)誤");return a / b;
}
void Func()
{int* ptr=new int;div();delete ptr;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}
Func()函數(shù)中我們?cè)诙焉仙暾?qǐng)了資源(new int對(duì)象),當(dāng)div()調(diào)用拋異常時(shí),程序會(huì)根據(jù)異常處理機(jī)制,找到最近的catch捕獲異常,這使得程序沒有進(jìn)行到釋放資源就結(jié)束(未delete int 對(duì)象),進(jìn)而會(huì)導(dǎo)致內(nèi)存泄漏。長(zhǎng)期運(yùn)行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺(tái)服務(wù)等等,出現(xiàn)內(nèi)存泄漏會(huì)導(dǎo)致響應(yīng)越來越慢,最終卡死,所以需要一定的方法來優(yōu)化。
異常處理機(jī)制
- 首先檢查throw本身是否在try塊內(nèi)部,如果是再查找匹配的catch語句。如果有匹配的,則調(diào)到catch的地方進(jìn)行處理。
- 沒有匹配的catch則退出當(dāng)前函數(shù)棧,繼續(xù)在調(diào)用函數(shù)的棧中進(jìn)行查找匹配的catch。
- 如果到達(dá)main函數(shù)的棧,依舊沒有匹配的,則終止程序。上述這個(gè)沿著調(diào)用鏈查找匹配的catch子句的過程稱為棧展開。所以實(shí)際中我們最后都要加一個(gè)catch(…)捕獲任意類型的異常,否則當(dāng)有異常沒捕獲,程序就會(huì)直接終止。
- 找到匹配的catch子句并處理以后,會(huì)繼續(xù)沿著catch子句后面繼續(xù)執(zhí)行。
常用的解決方案是:
1、事前預(yù)防型。如智能指針等。
2、事后查錯(cuò)型。如泄漏檢測(cè)工具。
概念
智能指針是一種特殊的指針類型,用于自動(dòng)管理動(dòng)態(tài)分配的內(nèi)存。相比于傳統(tǒng)的裸指針,智能指針提供了更安全、更方便的內(nèi)存管理方式。
智能指針的原理主要基于對(duì)象的生命周期管理和引用計(jì)數(shù)機(jī)制。通過在自動(dòng)釋放內(nèi)存和避免懸掛指針方面提供便利,它們可以幫助開發(fā)人員更輕松地處理動(dòng)態(tài)內(nèi)存分配和釋放的問題。
RAII(Resource Acquisition Is Initialization)
對(duì)象的生命周期控制程序資源的技術(shù),在對(duì)象構(gòu)造時(shí)獲取資源,在對(duì)象析構(gòu)時(shí)釋放資源。借此,我們實(shí)際上把管理一份資源的責(zé)任托管給了一個(gè)對(duì)象。這種做法有兩大好處:
1.不需要顯式地釋放資源。(對(duì)象析構(gòu)了資源就釋放了)
2.采用這種方式,對(duì)象所需的資源在其生命期內(nèi)始終保持有效。(資源和對(duì)象綁定)
分類
在C++98,有智能指針auto_ptr,但在C++11后,auto_ptr被棄用。標(biāo)準(zhǔn)庫中提供了三種常見的智能指針:unique_ptr、shared_ptr和weak_ptr。
auto_ptr
auto_ptr是一種獨(dú)占所有權(quán)的智能指針,實(shí)現(xiàn)原理是管理權(quán)的轉(zhuǎn)移。當(dāng)一個(gè)auto_ptr將其所有權(quán)轉(zhuǎn)移給另一個(gè)auto_ptr時(shí),原始的auto_ptr將不再擁有對(duì)資源的所有權(quán),即兩個(gè)auto_ptr不能管理同一份資源。
缺陷:
1.拷貝和賦值操作會(huì)改變資源的所有權(quán)
auto_ptr<string> p1(new string("hhe"));
auto_ptr<string> p2(new string("aab"));
p1=p2;
//p2賦值給p1后,p1釋放掉管理"hhe"的指針并接收p2交給它的管理"aab"的指針
//p2交付完畢后釋放自己內(nèi)部的指針并置NULL。
2.指針被懸空成野指針容易解引用造成錯(cuò)誤
auto_ptr<int> sp1(new int);
auto_ptr<int> sp2(sp1); // 管理權(quán)轉(zhuǎn)移
//sp1懸空
*sp2 = 10;
cout << *sp2 << endl;
cout << *sp1 << endl;
自C++11起,該類模板已被棄用,用更嚴(yán)謹(jǐn)?shù)膗nique_ptr 取代了auto_ptr
unique_ptr
unique_ptr對(duì)auto_ptr的缺陷做了修改,和auto_ptr類似,unique_ptr是一種獨(dú)占所有權(quán)的智能指針。它具有以下特點(diǎn):
- 提供了唯一擁有的語義,即同一時(shí)間只能有一個(gè)unique_ptr指向同一塊內(nèi)存。
- 當(dāng)unique_ptr被銷毀時(shí),會(huì)自動(dòng)釋放其所擁有的對(duì)象。
- 不允許多個(gè)unique_ptr指針共享同一塊內(nèi)存,避免了懸空指針和內(nèi)存泄漏的風(fēng)險(xiǎn)。
- 無法進(jìn)行左值復(fù)制賦值操作,但允許臨時(shí)右值賦值構(gòu)造和賦值
unique_ptr<string> p1(new string("hhe"));
unique_ptr<string> p2(new string("aab"));
p1 = p2; // 禁止左值賦值
unique_ptr<string> p3(p2); // 禁止左值賦值構(gòu)造
unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2); // 使用move把左值轉(zhuǎn)成右值就可以賦值了,效果和auto_ptr賦值一樣
下面給出unique_ptr指針的模擬實(shí)現(xiàn):
template<class T>
class unique_ptr
{
public:unique_ptr( T* ptr):_ptr(ptr){}~unique_ptr(){if(_ptr)delete _ptr;}T& operator*()return *_ptr;T* operator->()return _ptr;unique_ptr(const unique_ptr<T>& sp) =delete;unique_ptr<T>& operator =(const unique_ptr<T>& sp) =delete;
private:T* _ptr;
}
shared_ptr
為了解決一份資源只能被一個(gè)智能指針管理的問題,新增了shared_ptr智能指針。shared_ptr是一種共享所有權(quán)的智能指針。它使用引用計(jì)數(shù)的方式來跟蹤對(duì)象的引用數(shù)量。每次創(chuàng)建shared_ptr時(shí),引用計(jì)數(shù)增加;每次銷毀或重置shared_ptr時(shí),引用計(jì)數(shù)減少。當(dāng)引用計(jì)數(shù)變?yōu)?時(shí),shared_ptr會(huì)自動(dòng)釋放所管理的對(duì)象的內(nèi)存。
引用計(jì)數(shù)
因?yàn)閟hared_ptr支持一份資源被多個(gè)指針管理,未避免對(duì)一塊空間資源的重復(fù)釋放,shared_ptr不必在每次析構(gòu)時(shí)都釋放資源,而是在管理該資源的最后一個(gè)shared_ptr析構(gòu)時(shí)釋放資源。那如何知道一個(gè)資源被幾個(gè)shared_ptr管理?析構(gòu)時(shí)如何知道該指針是最后一個(gè)管理該資源的指針?這些問題需要在被管理的資源對(duì)象上裝一個(gè)計(jì)數(shù)器,供這些shared_ptr共享。
當(dāng)創(chuàng)建shared_ptr時(shí),會(huì)同時(shí)創(chuàng)建一個(gè)控制塊,并將所管理的對(duì)象指針和引用計(jì)數(shù)等信息存儲(chǔ)在該控制塊中。多個(gè)shared_ptr可以共享同一個(gè)控制塊,通過引用計(jì)數(shù)來追蹤所有引用該對(duì)象的shared_ptr個(gè)數(shù)。
template<class T>class shared_ptr{shared_ptr(T* ptr=nullptr) :_ptr(ptr), _pcount(new int(1)){}template<class D>shared_ptr(T* ptr, D del) :_ptr(ptr), _pcount(new int(1)) ,_del(del){}~shared_ptr(){if (--(*_pcount) == 0){cout << "delete:" << _ptr << endl;del(_ptr);delete _pcount;} }T& operator*(){return *_ptr;}T* operator->(){return _ptr;}shared_ptr(const shared_ptr<T>& sp) {_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (this != &sp){--(*_pcount);if (*_pcount == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;*_pcount++;}return *this;}int use_count(){return *_pcount;}T* get(){return _ptr;}private:T* _ptr;int* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr;};};
循環(huán)引用
shared_ptr在使用不當(dāng)容易引發(fā)循環(huán)引用問題。循環(huán)引用就是一組對(duì)象彼此持有對(duì)方的智能指針,從而導(dǎo)致它們的引用計(jì)數(shù)永遠(yuǎn)不會(huì)變?yōu)榱?。?dāng)存在循環(huán)引用時(shí),智能指針可能會(huì)導(dǎo)致內(nèi)存泄漏,因?yàn)檠h(huán)引用會(huì)阻止引用計(jì)數(shù)減到零。這將導(dǎo)致相關(guān)的內(nèi)存資源無法被釋放,從而造成內(nèi)存泄漏。
下面是一個(gè)產(chǎn)生了循環(huán)引用問題的示例:
struct Father
{
...//此處省略成員變量及函數(shù)shared_ptr<Child> child;
}
struct Child
{
...//此處省略成員變量及函數(shù)shared_ptr<Father> father;
}
//調(diào)用
shared_ptr<Father> a(new Father);
shared_ptr<Child> b(new Child);
a->child=b;
b->father=a;
循環(huán)計(jì)數(shù)情況如下圖所示:
weak_ptr
weak_ptr是一種弱引用的智能指針,通過使用weak_ptr作為循環(huán)引用中的其中一個(gè)對(duì)象,可以打破循環(huán)引用并允許相關(guān)對(duì)象被正確地銷毀。它可以觀測(cè)shared_ptr所管理的對(duì)象,并不會(huì)增加引用計(jì)數(shù)。當(dāng)最后一個(gè)shared_ptr被銷毀時(shí),即使還有weak_ptr存在,也不會(huì)阻止所管理對(duì)象的內(nèi)存釋放。(不參與資源的管理,解決智能指針交叉使用問題)
struct Father
{
...//此處省略成員變量及函數(shù)weak_ptr<Child> child;
}
struct Child
{
...//此處省略成員變量及函數(shù)weak_ptr<Father> father;
}
使用weak_ptr可以有效避免循環(huán)引用問題。