C++的動態內存的分配與釋放是個挺折磨人的事情,尤其異常分支復雜時(比如一堆try catch中,各catch里需要做delete 掉相關的堆上分配的內存),極有可能產生內存泄露的情況。C++中提供了智能指針作為可選的解決方案, C++標准庫中自帶的智能指針是auto_ptr,它在大多數場景下是滿足需求的。針對auto_ptr的缺點,boost和loki兩套庫都擴展出一些智能指針,並且boost中有兩位幸運兒入選了tr1中(std::tr1::shared_ptr,std::tr1::weak_ptr)。本文就gcc中auto_ptr的實現做些分析,以饗自己。筆記采用注釋源碼的方式。
/**
* 這個wrapper類提供auto_ptr以引用語義,在下面的操作中有介紹。
*/
template<typename _Tp1>
struct auto_ptr_ref {
_Tp1* _M_ptr;
explicit auto_ptr_ref(_Tp1* __p) :
_M_ptr(__p) {
}
};
/**
* auto_ptr的實現還是很簡單的,使用上也簡單。在創建auto_ptr對象后,
* 通常的使用也就是調用它的*和->操作符,如下面的sample片段:
* AutoPtr<Admin> ptr1(new Admin());
* cout<<ptr1->getAge()<<endl;
* cout<<”obj:”<<*ptr1<<endl;
* 可以看到,auto_ptr中的成員函數都是throw()不拋異常的。
*/
template<typename _Tp>
class auto_ptr {
private:
_Tp* _M_ptr;//
public:
/// The pointed-to type.
typedef _Tp element_type;
/**
* 構造函數,將auto_ptr綁定到指針__p。
* __p是一個指向new出來的對象的指針,默認為0(NULL)是說auto_ptr的構造函數可以不傳參構造,
* 這時成員_M_ptr=0,如果接着解引用auto_ptr對象,將Segmentation fault。當然,通常應用auto_ptr的構造
* 函數會傳參的。auto_ptr提供了get函數來判斷_M_ptr是否為空、reset函數重置_M_ptr指針。
* 在繼承情況下,_M_ptr可以是__p的基類型。
* 構造函數聲明為explicit表示禁止參數的自動類型轉換(因為它們總是邪惡的)。
*
*/
explicit auto_ptr(element_type* __p = 0) throw () :
_M_ptr(__p) {
}
/**
* 輔助函數
*/
element_type* release() throw () {
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
/**
* auto_ptr的復制構造函數是很邪惡的,它的邏輯是將參數__a中的指針挪給新對象的,
* 原來的__a的內置指針被置空,接下來就不能繼續操作__a引用的對象,否則就掉進出錯的陷阱。
* 另外,復制構造函數的參數不是個const,因為它需要修改參數內容的。
*/
auto_ptr(auto_ptr& __a) throw () :
_M_ptr(__a.release()) {
}
/**
* 成員函數模板。好吧,我承認,我對模板也是半知半解(注意,不是一知半解)。這個函數
* 用於將繼承體系中的子類型上溯成基類型。舉個例子:
* 假如User是基類,Admin是派生類,那么下面的操作是ok的。
* auto_ptr<Admin> ptr2(new Admin());
* auto_ptr<User> ptr3(ptr2);
* 編譯器的原理類型識別和轉換的大致過程是:當模板參數類型不匹配時(_Tp1轉成_Tp),
* 編譯首先檢查是否存在合適的類型轉換操作符(auto_ptr是沒有的),如果沒有則檢查是否
* 存在合適的成員函數模板完成類型轉換。在_Tp1是_Tp的派生類的情況下,這種轉換就會成功。
* 這也是說,編譯器檢查的是模板參數類型而不是對象的實際類型,所以,下面的例子編譯就會失敗:
* auto_ptr<User> ptr2(new Admin());
* auto_ptr<Admin> ptr3(ptr2);
*/
template<typename _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) throw () :
_M_ptr(__a.release()) {
}
/**
* @brief 重置管理的對象指針,如果_M_ptr不為空,會delete掉。如果重置的指針就是本身
* 的_M_ptr,就是個空操作。
* @param __p 對象指針.
*/
void reset(element_type* __p = 0) throw () {
if (__p != _M_ptr) {
delete _M_ptr;
_M_ptr = __p;
}
}
/**
* 賦值操作符,和復制構造函數一樣是邪惡的,賦值操作會delete掉右值管理的對象指針。
* 如果auto_ptr對象作為函數參數傳遞,並且是傳值,那么這個調用過程會涉及到賦值操作符
* 的調用,產生並不期待的delete外部對象的結果。
*/
auto_ptr& operator=(auto_ptr& __a) throw () {
reset(__a.release());
return *this;
}
/**
* 成員函數模板賦值操作符
*/
template<typename _Tp1> auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw () {
reset(__a.release());
return *this;
}
/**
* 析構函數,操作很明顯,因為是delete,所以auto_ptr是不支持數組指針的,否則會有
* 內存泄露。
*/
~auto_ptr() {
delete _M_ptr;
}
/**
* 解引用操作符
*/
element_type& operator*() const throw () {
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}
/**
* ->操作符,是auto_ptr被用得最多的調用吧。
*/
element_type* operator->() const throw () {
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}
/**
* 返回auto_ptr管理的指針,這通常用於判斷指針是否為空的情況,所以,如果要判斷
* auto_ptr管理的指針是否為空,不要使用if(auto_ptr_obj){}而是使用get函數(實際上,
* 因為auto_ptr並沒用定義指向element_type的dumb指針的隱式類型轉換操作符,所以根本
* 編譯不過if(auto_ptr_obj))。
* 但是,auto_ptr並沒有禁止你進一步操作你得到的指針,甚至delete它使
* auto_ptr對象內置的指針懸空。
*/
element_type* get() const throw () {
return _M_ptr;
}
/**
* 下面的三個函數連帶auto_ptr_ref是auto_ptr中的神奇之筆,因為我拍了好多次腦袋才想明白
* 是怎么的應用原理。考慮兩種使用情況:
* 1)void foo(auto_ptr< User> ptr);
* 2)auto_ptr< User> func_returning_auto_ptr(…..);
* auto_ptr ptr< User> = func_returning_auto_ptr(…..);
* 對於第一種情況,當調用方式是:foo(auto_ptr< User>(new User));時,因為是傳值調用,
* 而實參是個臨時對象,所以需要做賦值構造對象,但auto_ptr的賦值構造函數參數並不是const的
* 所以不匹配其復制構造函數。auto_ptr采用了曲線策略,編譯器接着檢查類型轉換操作符,
* 發現operator auto_ptr_ref<_Tp1>()是匹配的,所以將臨時對象轉成auto_ptr_ref,再調用
* auto_ptr(auto_ptr_ref __ref)把auto_ptr_ref轉成auto_ptr。
*/
auto_ptr(auto_ptr_ref<element_type> __ref) throw () :
_M_ptr(__ref._M_ptr) {
}
template<typename _Tp1> operator auto_ptr_ref<_Tp1>() throw () {
return auto_ptr_ref<_Tp1> (this->release());
}
/**
* 和auto_ptr(auto_ptr_ref __ref)相似
*/
auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw () {
if (__ref._M_ptr != this->get()) {
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return *this;
}
/**
* 怎么說這個函數呢?我還不曉得這個轉換操作符在什么時候調用呢?
*/
template<typename _Tp1> operator auto_ptr<_Tp1>() throw () {
return auto_ptr<_Tp1> (this->release());
}
};
//auto_ptr是不支持void類型的模板特化。
template<> class auto_ptr<void> {
public:
typedef void element_type;
};