智慧指標的分析與實現
一,為什麼要用智慧指標
在編寫c++程式的時候,讓我們最頭痛的問題就是記憶體洩露,也就是說
int* pt = new int;
delete pt;
必須保證new和delete必須成對出現。作為程式猿,可以像使用普通變數一樣來使用指標,這個指標可以在恰當的時候被自動釋放,智慧指標就是這樣一個指標,它的任務是保證每一個被動態分配的記憶體都能夠被釋放。
看一個例子
如果在DealProcessAdoption有一個異常,會發生什麼事情。
所以這段程式碼很危險,當DealProcessAdoption有一個exception,後面的delete程式碼就會跳過,從而造成記憶體洩露。
有兩種解決方案
一種是用try catch,另外一種就是用智慧指標。二,一個智慧指標的實際例子
class intptr
{
private:
int* m_p;
public:
intptr(int* p){ m_p = p; }
~intptr(){ delete m_p; }
int& operator*(){ return *m_p; }
};
我們可以方便的執行以下程式碼,而不必擔心記憶體洩漏的問題:
somefunction()
{
intptr pi(new int);
*pi = 10;
int a = *pi;
}
以上我們給出的“智慧指標”有個致命錯誤。設想我們執行以下程式碼會有怎樣的情況發生:
void somefunction()
{
intptr pt1(new int);
intptr pt2(new int);
*pt1 = 10;
pt2 = pt1;
}
對於普通指標來說,pt2 = pt1只是讓pt2指向與pt1相同的地址,但是對於我們的智慧指標來說,pt2原先指向的地址被洩露掉了,而pt1所指向的地址被釋放了兩次。所以,我們給每個new出來的記憶體地址對應的分配一個“被指向計數器”,由它記錄這塊記憶體地址被多少指標所指向。
三,boost庫中的智慧指標
但是這個auto_ptr有很多缺點
1、auto_ptr不能共享所有權。
2、auto_ptr不能指向陣列
3、auto_ptr不能作為容器的成員。
4、不能通過賦值操作來初始化auto_ptr
std::auto_ptr<int> p(newint(42)); //OK
std::auto_ptr<int> p = newint(42); //ERROR
這是因為auto_ptr 的建構函式被定義為了explicit,不能隱式呼叫
5、不要把auto_ptr放入容器
boost庫的share_ptr
shared_ptr是Boost庫所提供的一個智慧指標的實現,shared_ptr就是為了解決auto_ptr在物件所有權上的侷限性(auto_ptr是獨佔的),在使用引用計數的機制上提供了可以共享所有權的智慧指標.
2. shared_ptr比auto_ptr更安全
3. shared_ptr是可以拷貝和賦值的,拷貝行為也是等價的,並且可以被比較,這意味這它可被放入標準庫的一般容器(vector,list)和關聯容器中(map)。
四,智慧指標的兩種實現方式
方案一(引入輔助類)
方案二(控制代碼類)
測試:
智慧指標的另外一種標準實現
namespace smart
{
// 引用計數類.
class smart_count
{
public:
smart_count(int c = 0) : use_count(c) {}
~smart_count() {}
// 增加引用計數, 並返回計數值.
int addref() { return ++use_count; }
// 減少引用計數, 並返回計數值.
int release() { return --use_count; }
private:
// 計數變數.
int use_count;
};
// 智慧指標.
template <class T>
class smart_ptr
{
public:
// 構造指標, 並使引用計數置為1.用explicit是防止隱式轉換
explicit smart_ptr (T* ptr) : p(ptr), u(new smart_count(1))
{}
// 構造空指標.
explicit smart_ptr () : p(NULL), u(NULL)
{}
// 智慧指標析構.
~smart_ptr (void)
{
// 如果引用計數等於0, 則刪除資料和引用計數, 並置p為NULL.
// 此處需要注意的是, 共用的u並未置為 NULL, 在其它指標析構
// 時, p為NULL, 則不會重複delete.
if (p && u->release() <= 0)
{
delete p;
delete u;
p = NULL;
}
}
// 智慧指標拷貝建構函式.
smart_ptr (const smart_ptr<T>& t)
{
p = t.p;
u = t.u;
if (u) // 必須判斷空值.
{
u->addref(); // 增加引用計數.
}
}
// 指標賦值.
void operator= (smart_ptr<T>& t)
{
// 首先將引用計數減1, 然後再判斷是否小於0, 如果小於0, 則delete.
if (p && u->release() <= 0)
{
delete p;
delete u;
}
// 直接賦值.
p = t.p;
u = t.u;
if (u) // 必須判斷空值.
{
u->addref(); // 增加引用計數.
}
}
// 過載->操作和*操作符.
T *operator-> (void) { return p; }
T& operator *(void) { return *p; }
// 過載!操作符.
bool operator! () const { return !p;}
// 過載指標bool值操作符.
typedef smart_ptr<T> this_type;
typedef T * this_type::*unspecified_bool_type;
operator unspecified_bool_type() const { return !p ? 0: &this_type::p; }
// 得到原指標.
T* get() { return p; }
void reset(T* ptr)
{
// 首先將引用計數減1, 然後再判斷是否小於0, 如果小於0, 則delete.
if (p && u->release() <= 0)
{
delete p;
delete u;
}
// 賦值, 如果是NULL, 則不建立引用計數.
p = ptr;
if (p)
u = new smart_count(1);
else
u = NULL;
}
void reset(smart_ptr<T>& t)
{
// 首先將引用計數減1, 然後再判斷是否小於0, 如果小於0, 則delete.
if (p && u->release() <= 0)
{
delete p;
delete u;
}
// 賦值.
p = t.p;
u = t.u;
if (u) // 必須判斷空值.
{
u->addref(); // 增加引用計數.
}
}
private:
T* p;
smart_count* u;
};
// 過載==操作符.
template<class T, class U> inline bool operator==(smart_ptr<T> & a, smart_ptr<U> & b)
{
return a.get() == b.get();
}
// 過載!=操作符.
template<class T, class U> inline bool operator!=(smart_ptr<T> & a, smart_ptr<U> & b)
{
return a.get() != b.get();
}
}