C++11 智能指針的具體使用
智能指針的原理
RAII
RAII(Resource Acquisition Is Initialization)是一種利用對象生命周期來控制程序資源(如內(nèi)存、文件句柄、網(wǎng)絡(luò)連接、互斥量等等)的簡單技術(shù)。
在對象構(gòu)造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內(nèi)始終保持有效,最后在對象析構(gòu)的時候釋放資源。 借此,我們實際上把管理一份資源的責(zé)任托管給了一個對象。這種做法有兩大好處:
- 不需要顯式地釋放資源。
- 采用這種方式,對象所需的資源在其生命期內(nèi)始終保持有效。
我們使用RAII的思想設(shè)計SmartPtr類:
template <class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
};
智能指針的原理
上述的SmartPtr還不能將其稱為智能指針,因為它還不具有指針的行為。指針可以解引用,也可以通過->去訪問所指空間中的內(nèi)容 ,因此:SmartPtr模板類中還得需要將* 、->重載下,才可讓其像指針一樣去使用。
template <class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
};
智能指針使用:

總結(jié)智能指針的原理:
- RAII特性
- 重載operator*和opertaor->,具有像指針一樣的行為。
auto_ptr
1.auto_ptr的使用及問題
auto_ptr的頭文件#include<memory>
auto_ptr的使用:


為什么此時訪問sp的成員時會報錯呢?我們來看看它們的地址。

我們發(fā)現(xiàn)在拷貝構(gòu)造之后,sp管理的地址為空,而sp1管理的地址是之前sp所管理的地址,管理權(quán)發(fā)生了轉(zhuǎn)移。那么上面所說的報錯也很容易想通,因為sp管理的地址為空,不能進(jìn)行訪問。
auto_ptr的問題:當(dāng)對象拷貝或者賦值后,管理權(quán)進(jìn)行轉(zhuǎn)移,造成前面的對象懸空。auto_ptr問題是非常明顯的,所以實際中很多公司明確規(guī)定了不能使用auto_ptr。
auto_ptr的實現(xiàn)原理:管理權(quán)轉(zhuǎn)移的思想,下面簡化模擬實現(xiàn)了一份AutoPtr來了解它的原理:
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
//拷貝:管理權(quán)轉(zhuǎn)移
AutoPtr(AutoPtr<T> &sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
//賦值:管理權(quán)轉(zhuǎn)移
AutoPtr& operator=(AutoPtr<T> &sp)
{
if (this != &sp)
{
if (_ptr)
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
};
unique_ptr
為了解決拷貝或者賦值時管理權(quán)轉(zhuǎn)移的問題,出現(xiàn)了unique_ptr。
unique_ptr解決問題的方式非常粗暴:防拷貝,也就是不讓賦值和拷貝
unique_ptr的使用:

unique_ptr的實現(xiàn)原理:簡單粗暴的防拷貝,下面簡化模擬實現(xiàn)了一份UniquePtr來了解它的原理:
template<class T>
class UniquePtr
{
public:
UniquePtr(T* ptr)
:_ptr(ptr)
{}
// C++11防拷貝的方式:delete
UniquePtr(const UniquePtr<T> &) = delete;
UniquePtr& operator=(const UniquePtr<T>&) = delete;
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
~UniquePtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
private:
//C++98防拷貝的方式:只聲明不實現(xiàn)+聲明成私有
//UniquePtr(UniquePtr<T> const &);
//UniquePtr& operator=(UniquePtr<T> const &);
T* _ptr;
};
shared_ptr
c++11中提供更靠譜的并且支持拷貝的shared_ptr
shared_ptr的使用

shared_ptr中拷貝與賦值都是沒有問題的。
shared_ptr的原理
- shared_ptr的原理:是通過引用計數(shù)的方式來實現(xiàn)多個shared_ptr對象之間共享資源。shared_ptr在其內(nèi)部,給每個資源都維護(hù)了著一份計數(shù),用來記錄該份資源被幾個對象共享。
- 在對象被銷毀時(也就是析構(gòu)函數(shù)調(diào)用),就說明自己不使用該資源了,對象的引用計數(shù)減一。
- 如果引用計數(shù)是0,就說明自己是最后一個使用該資源的對象,必須釋放該資源;
- 如果不是0,就說明除了自己還有其他對象在使用該份資源,不能釋放該資源,否則其他對象就成野指針了。
shared_ptr中成員函數(shù):use_count(對象數(shù)據(jù)的引用計數(shù))
示例:

示例詳解:

利用引用計數(shù)簡單的實現(xiàn)SharedPtr,了解原理:
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
{}
SharedPtr(const SharedPtr<T> &sp)
:_ptr(sp._ptr)
,_count(sp._count)
{
//計數(shù)器累加
++(*_count);
}
SharedPtr& operator=(const SharedPtr<T> &sp)
{
//判斷管理的是否是同一份資源
if (_ptr != sp._ptr)
{
//計數(shù)-1,判斷之前管理的資源是否需要釋放
if ((--(*_count)) == 0)
{
delete _ptr;
delete _count;
}
_ptr = sp._ptr;
_count = sp._count;
//計數(shù)器累加
++(*_count);
}
return *this;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
~SharedPtr()
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
_ptr = nullptr;
_count = nullptr;
}
}
private:
T* _ptr;
int* _count;//給每份資源開辟一個計數(shù)器
};
但是還存在一個線程安全的問題:
- 智能指針對象中引用計數(shù)是多個智能指針對象共享的,兩個線程中智能指針的引用計數(shù)同時++或–,這個操作不是原子的,引用計數(shù)原來是1,++了兩次,可能還是2。這樣引用計數(shù)就錯亂了。會導(dǎo)致資源未釋放或者程序崩潰的問題。所以只能指針中引用計數(shù)++、- -是需要加鎖的,也就是說引用計數(shù)的操作是線程安全的。
- 智能指針管理的對象存放在堆上,兩個線程中同時去訪問,會導(dǎo)致線程安全問題。
這里我們通過加鎖來解決線程安全問題:
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
,_mutex(new mutex)
{}
SharedPtr(const SharedPtr<T> &sp)
:_ptr(sp._ptr)
,_count(sp._count)
,_mutex(sp._mutex)
{
//計數(shù)器累加
AddCount();
}
SharedPtr& operator=(const SharedPtr<T> &sp)
{
//判斷管理的是否是同一份資源
if (_ptr != sp._ptr)
{
//計數(shù)-1,判斷之前管理的資源是否需要釋放
if (SubCount() == 0)
{
delete _ptr;
delete _count;
delete _mutex;
}
_ptr = sp._ptr;
_count = sp._count;
_mutex = sp._mutex;
//計數(shù)器累加
AddCount();
}
return *this;
}
//線程安全的累加器
int AddCount()
{
//加鎖
_mutex->lock();
++(*_count);
_mutex->unlock();
return *_count;
}
int SubCount()
{
_mutex->lock();
--(*_count);
_mutex->unlock();
return *_count;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
~SharedPtr()
{
if (SubCount() == 0)
{
delete _ptr;
delete _count;
delete _mutex;
_ptr = nullptr;
_count = nullptr;
_mutex = nullptr;
}
}
private:
T* _ptr;
int* _count;//給每份資源開辟一個計數(shù)器
mutex* _mutex; //每一份資源有一個獨立的鎖
};
shared_ptr的循環(huán)引用
循環(huán)引用的場景:
struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
void test()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
}

node1和node2兩個智能指針對象指向兩個節(jié)點,兩個節(jié)點的引用計數(shù)都是1。node1->next指向node2,node2->prev指向node1,兩個節(jié)點的引用計數(shù)都變成2。程序運行完之后,析構(gòu)node1和node2,node1和node2所指向的節(jié)點引用計數(shù)分別減1,但是node1->next指向下面節(jié)點,node2->prev指向上面節(jié)點,此時,兩個節(jié)點的引用計數(shù)都為1,所以兩個節(jié)點不能析構(gòu)。
引用計數(shù)為0時,如果要析構(gòu)node1節(jié)點,就先要去析構(gòu)node1中的自定義結(jié)構(gòu),然后再析構(gòu)node1。也就是說node1->next析構(gòu)了,node2就釋放了;node2->prev析構(gòu)了,node1就釋放了。但是_next屬于node的成員,node1釋放了,_next才會析構(gòu),而node1由_prev管理,_prev屬于node2成員,所以這就叫循環(huán)引用,誰也不會釋放。
解決方案:
在引用計數(shù)的場景下,把節(jié)點中的_prev和_next改成weak_ptr就可以了
原理就是,node1->_next = node2和node2->_prev = node1時weak_ptr的_next和_prev不會增加node1和node2的引用計數(shù)。
weak_ptr最大作用就是解決shared_ptr的循環(huán)引用
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
void test()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
}
注意:
weak_ptr不能單獨使用,可以用shared_ptr創(chuàng)建
//weak_ptr錯誤使用 weak_ptr<ListNode> node1(new ListNode); //weak_ptr正確使用 shared_ptr<ListNode> node2(new ListNode); weak_ptr<ListNode> node3(node2);
到此這篇關(guān)于C++11 智能指針的具體使用的文章就介紹到這了,更多相關(guān)C++11 智能指針內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言編程中從密碼文件獲取數(shù)據(jù)的函數(shù)總結(jié)
這篇文章主要介紹了C語言編程中從密碼文件獲取數(shù)據(jù)的函數(shù)總結(jié),包括getpw()函數(shù)和getpwnam()函數(shù)以及getpwuid()函數(shù),需要的朋友可以參考下2015-08-08
C++?opencv利用grabCut算法實現(xiàn)摳圖示例
這篇文章主要為大家介紹了C++?opencv利用grabCut算法實現(xiàn)摳圖的代碼示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管
這篇文章主要介紹了使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管,本文章內(nèi)容詳細(xì),具有很好的參考價值,希望對大家有所幫助,需要的朋友可以參考下2023-01-01
C語言去除相鄰重復(fù)字符函數(shù)的實現(xiàn)方法
這篇文章主要介紹了C語言去除相鄰重復(fù)字符函數(shù)的實現(xiàn)方法的相關(guān)資料,實現(xiàn)去重字符串相鄰重復(fù)的字符,不相鄰的不用去重的功能,需要的朋友可以參考下2017-08-08
c++ Protobuf解決數(shù)據(jù)傳輸瓶頸面試精講
這篇文章主要介紹了c++ Protobuf解決數(shù)據(jù)傳輸瓶頸利器面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
VisualStudio2022 cmake配置opencv開發(fā)環(huán)境
本文主要介紹了VisualStudio2022 cmake配置opencv開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08

