一、有關智能指針的基本資訊 (一)智能指針是RAII思想的一個産品 RAII(資源獲得即初始化),定義一個類來封裝資源的配置設定和釋放,在構造函數完成資源的配置設定和初始化,在析構函數完成資源的清理,可以保證資源的正确初始化和釋放。 所謂智能指針,就是智能化(自動化)管理指針所指向的動态資源的釋放。 (二)為什麼要有智能指針 eg1:
void Test1()
{
int* p=new int(1);
if(1)
{
return;
}
delete p; //未執行
}
eg2:
void DoSomeTing()
{
if(1)
{
throw 1;
}
}
void Test2()
{
int* p1=new int(2);
DoSomeTing();
delete p1; //未執行
}
int main()
{
try
{
Test2();
}
catch(...)
{
cout<<"未知異常"<<endl;
}
return 0;
}
要解決此類問題,就需要使用智能指針。
二、智能指針 Boost庫中的智能指針:
- 管理權轉移(帶缺陷的設計)——>auto_ptr
- 防拷貝(簡單粗暴)——>scoped_ptr
- 引用計數(更實用更複雜)——>shared_ptr
C++11庫中引入了unique_ptr(相當于scoped_ptr)、shared_ptr和weak_ptr。 (一)auto_ptr 先實作一個最簡單的智能指針AutoPtr:
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
~AutoPtr()
{
if(_ptr)
{
delete _ptr;
_ptr=NULL;
}
}
protected:
T* _ptr;
};
利用該智能指針,可以将上述的eg1和eg2改正為:
void Test1()
{
int* p=new int(1);
AutoPtr<int> ap(p);
if(1)
{
return;
}
}
void DoSomeTing()
{
if(1)
{
throw 1;
}
}
void Test2()
{
int* p1=new int(2);
AutoPtr<int> ap(p1);
DoSomeTing();
}
int main()
{
try
{
Test1();
Test2();
}
catch(...)
{
cout<<"未知異常"<<endl;
}
return 0;
}
但這種智能指針是存在缺陷的。 如果将main函數換為下述内容,則會使程式崩潰。
int main()
{
AutoPtr<int> ap1(new int(1));
AutoPtr<int> ap2(ap1);
return 0;
}
這是因為ap1和ap2兩個對象指向了同一個地方,導緻該地方要被析構兩次。 需要加入管理權轉移的功能(加入拷貝構造函數如下):
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr=NULL;
}
//此時屬于淺拷貝,ap2(ap1)時,轉移後ap1被置空,再使用時程式崩潰。說明此時智能指針無法完全實作指針的作用。
//至于該構造沒有使用const,是因為_ptr不是const型的,因而設為const類型後無法指派。
//另外,一般傳引用時使用const是不希望改變原來的,但這裡需要修改,是以不加const
//(此時使用深拷貝也不可以,因為開辟兩塊空間的話就是兩個智能指針,而重載的目的是希望它是一個指針。)
最後,為了完整實作AutoPtr,還需要加入運算符重載函數:
AutoPtr<T>& operator=(AutoPtr<T>& ap) //指派運算符重載
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*() //解引用操作符重載
{
return*_ptr;
}
T* operator->() //->操作符重載(管理的是結構體的智能指針時)
{
return_ptr;
}
Auto_ptr雖然能夠通過管理權轉移解決多對象指向同一地方的問題,但仍是一種有缺陷的智能指針,很多時候不允許使用。 (二)scoped_ptr scoped_ptr的模拟實作如下:
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
if(_ptr)
{
cout<<"delete"<<_ptr<<endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
ScopedPtr(ScopedPtr<T>& sp); //隻聲明不實作
ScopedPtr<T>& operator=(ScopedPtr<T>& sp); //隻聲明不實作
prtected:
T* _ptr;
};
scoped_ptr為了防止拷貝,直接不允許拷貝,簡單粗暴,但也不完美。 (三)shared_ptr 對于多對象指向同一地方的問題,前面已經介紹了管理權轉移和防拷貝兩種方法,而shared_ptr介紹了第三種解決方法——引入一個引用計數對象。 對此方法可依據下圖進行了解:

shared_ptr的模拟實作如下:
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new long(1))
{}
~SharedPtr()
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
}
SharedPtr(SharedPtr<T>& sp) //拷貝構造,需要注意使計數加1
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
++(*_pCount);
}
SharedPtr<T>& operator=(SharedPtr<T>&sp) //指派運算符的重載
{
return*this;
}
T& operator*() //重載*
{
return*_ptr;
}
T* operator->() //重載->
{
return_ptr;
}
longUseCount() //傳回引用計數的值,友善檢視,例如: cout<<"sp1:"<<sp1.UseCount()<<endl;
{
return*_pCount;
}
protected:
T* _ptr;
long* _pCount;
};
void TestSharedPtr()
{
SharedPtr<int> sp1(new int(1));
SharedPtr<int> sp2(sp1);
}
int main()
{
TestSharedPtr();
system("pause");
return 0;
}
但對于指派運算,其實有三種情況:
而上述的shared_ptr模拟實作代碼隻能解決前兩種,需要對拷貝構造函數進行改進。
SharedPtr<T>& operator=(SharedPtr<T>&sp) //改進後的拷貝構造
{
if(_ptr != sp._ptr)
{
if(--(*_pCount) == 0)
{
delete_ptr;
delete_pCount;
}
_ptr =sp._ptr;
_pCount =sp._pCount;
++(*_pCount);
}
return*this;
}
void TestSharedPtr() //對上述三種情況進行測試,都可通過
{
SharedPtr<int> sp1(new int(1));
SharedPtr<int> sp2(sp1);
SharedPtr<int> sp3(sp2);
sp1 = sp1;
sp1 = sp2;
SharedPtr<int> sp4(new int(2));
sp1 = sp4;
}