深入解析C++中的智能指針
一、RAII和智能指針
RAII是Resource Acquisition Is Initialization的縮寫,它的意思是獲取資源立即初始化。本質是?種利用對象生命周期來管理獲取到的動態(tài)資源,避免資源泄漏
智能指針類除了滿足RAII的設計思路,還要方便對資源進行訪問,所以智能指針類還會想迭代器類?樣,重載 operator*/operator->/operator[]等運算符,
例:
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++標準庫智能指針的使用
C++標準庫中的智能指針都在< memory >這個頭文件下面,因此在使用前需要包含這一頭文件。除了weak_ptr以外,其它都符合RAII和像指針?樣訪問的行為,原因主要是解決智能指針拷貝時的思路不同
C++標準庫主要提供了四種智能指針:
auto_ptr: 它是在C++98中設計出來的智能指針,其特點是拷貝時把被拷貝對象的資源管理權轉移給拷貝對象,這個設計是非常糟糕的,因為它會讓被拷貝的對象懸空,當我們對其進行訪問時會發(fā)生報錯。因此在日常工作中不建議使用這一指針
以下這三種指針都是在C++11中設計出來的
unique_ptr:它的意思是唯一指針,其特點是不支持拷貝只支持移動,因此在不需要拷貝的場景下就可以使用它
shared_ptr: 它的意思是共享指針,其特點是支持拷貝也支持移動,因此在需要拷貝的場景下就可以使用它
weak_ptr:它的意思的弱指針,其完全不同于上面的智能指針,因為它不支持RAII,也就意味著不能用它直接管理資源。其作用是用來解決循環(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的管理權轉移給ap2
auto_ptr<Date> ap2(ap1);
//報錯:對空指針進行訪問
ap1->_year++;
unique_ptr<Date> up1(new Date);
//報錯:不支持拷貝
unique_ptr<Date> up2(up1);
//支持移動,移動后up1也為空,不能對其進行訪問
unique_ptr<Date> up2(move(up1));
shared_ptr<Date> sp1(new Date);
//支持拷貝也支持移動
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(move(sp1));
return 0;
}刪除器
智能指針析構時默認是進行delete釋放資源,這也就意味著如果不是new出來的資源交給智能指針管理,析構時就會崩潰。因此智能指針支持在構造時給?個刪除器,而刪除器本質就是?個可調用對象,這個可調用對象中實現你想要的釋放資源的方式。即當我們在構造智能指針時,給了定制的刪除器,智能指針析構時就會調用我們設計出的刪除器去釋放資源
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()
{
//這樣去構造程序會報錯
//unique_ptr<Date> up(new Date[10]);
//shared_ptr<Date> sp(new Date[10]);
//解決方案1:因為new[]經常使用,因此unique_ptr和shared_ptr
//實現了?個特化版本,這個特化版本在析構時用的delete[]
unique_ptr<Date[]> up(new Date[10]);
shared_ptr<Date[]> sp(new Date[10]);
//解決方案2:使用仿函數對象做刪除器
//這里需要注意:unique_ptr和shared_ptr在支持刪除器的方式是有所不同的
//unique_ptr在類模板參數支持的,shared_ptr是構造函數參數支持的
unique_ptr<Date, DeleteArray<Date>> up1(new Date[10]);
shared_ptr<Date> sp1(new Date[10], DeleteArray<Date>());
// 函數指針做刪除器
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);
// lambda表達式做刪除器
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;
}使用時需要注意以下幾點:
1.shared_ptr 除了支持用指向資源的指針構造,還支持 make_shared 用初始化資源對象的值直接構造。
2.shared_ptr 和 unique_ptr 都支持了operator bool的類型轉換,如果智能指針對象是?個空對象沒有管理資源,則返回false,否則返回true,意味著我們可以直接把智能指針對象給if判斷是否為空。
3.shared_ptr 和 unique_ptr 在構造函數中都得使用explicit來修飾,防止普通指針隱式類型轉換成智能指針對象。
例:
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;
}三、智能指針的原理及其模擬實現
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是防止普通指針隱式類型
// 轉化為智能指針對象
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;
}
//不支持左值的拷貝構造
unique_ptr(const unique_ptr<T>& up) = delete;
//不支持左值的賦值
unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
//支持右值的拷貝構造
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.重點:shared_ptr
shared_ptr的拷貝底層是采用引用計數的方式來實現的,讓多個shared_ptr對象共用同一份資源

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之間的關系
1.shared_ptr循環(huán)引用的問題
shared_ptr在大多數情況下管理資源都非常合適,但在循環(huán)引用場景下會出現內存泄漏的問題,因此我們要學會使用weak_ptr來解決這類問題

當n1和n2進行析構時,引用計數為1不為0,導致內存泄漏

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

五、C++11和boost智能指針中的關系
Boost庫是為C++語言標準庫提供擴展的?些C++程序庫的總稱,C++11及之后的新語法和庫有很多都是從Boost中來的
C++98中產生了第?個智能指針auto_ptr
C++boost庫也給出了更實用的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等
C++TR1,引入了shared_ptr等,不過注意的是TR1并不是標準版
C++11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr對應boost的scoped_ptr。并且這些智能指針的實現原理是參考boost中的實現的
六、內存泄漏
1.什么是內存泄漏以及造成的危害
內存泄漏指因為疏忽或錯誤造成程序未能釋放已經不再使用的內存,?般是忘記釋放或者發(fā)生異常釋放程序未能執(zhí)行導致的。
危害:普通程序在資源少的情況下運行?會就結束了出現內存泄漏問題也不是很大。但在長期運行的程序以及有大量資源的情況下出現內存泄漏,影響是非常大的,如操作系統、后臺服務、長時間運行的客戶端等等,不斷出現內存泄漏會導致可用內存不斷變少,各種功能響應越來越慢,最終卡死。
2.如何避免內存泄漏
1.養(yǎng)成良好的編碼規(guī)范,申請的內存空間記著匹配的去釋放
2.盡量使用智能指針來管理資源,如果在某些場景比較特殊的情況下,采用RAII思想自己造個輪子來管理
3.定期使用內存泄露工具
到此這篇關于C++智能指針介紹的文章就介紹到這了,更多相關C++智能指針內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解析C++中的for循環(huán)以及基于范圍的for語句使用
這篇文章主要介紹了解析C++中的for循環(huán)以及基于范圍的for語句使用,是C++入門學習中的基礎知識,需要的朋友可以參考下2016-01-01
c語言中單引號和雙引號的區(qū)別(順利解決從字符串中提取IP地址的困惑)
c語言中的單引號和雙引號可是有很大區(qū)別的,使用之前一定要了解他們之間到底有什么不同,下面小編就給大家詳細的介紹一下吧,對此還不是很了解的朋友可以過來參考下2013-07-07

