scoped_ptr原始碼剖析
scoped_ptr是一個很類似auto_ptr的智慧指標,它包裝了new操作符在堆上分配的動態物件,能夠保證動態建立的物件在任何時候都可以被正確地刪除。但scoped_ptr的所有權更加嚴格,不能轉讓,一旦scoped_ptr獲取了物件的管理權,你就無法再從它那裡取回來(reset函式是重置,會清空自己,同樣無法取回)。
原始碼剖析
原始碼如下:
namespace boost
{
// scoped_ptr mimics a built-in pointer except that it guarantees deletion
// of the object pointed to, either on destruction of the scoped_ptr or via
// an explicit reset(). scoped_ptr is a simple solution for simple needs;
// use shared_ptr or std::auto_ptr if your needs are more complex.
template<class T> class scoped_ptr // noncopyable
{
private:
T * px;
//禁止copy assignment
scoped_ptr(scoped_ptr const &);
scoped_ptr & operator=(scoped_ptr const &);
typedef scoped_ptr<T> this_type;
//禁止比較
void operator==( scoped_ptr const& ) const;
void operator!=( scoped_ptr const& ) const;
public:
typedef T element_type;
//使用普通指標構造,禁止隱式轉換
explicit scoped_ptr( T * p = 0 ): px( p ) // never throws
{
}
#ifndef BOOST_NO_AUTO_PTR
//噗,scoped_ptr是可以從auto_ptr那裡奪取過來的,不過auto_ptr呼叫了releadse自然就失效了
explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT : px( p.release() )
{
}
#endif
~scoped_ptr() // never throws
{
boost::checked_delete( px ); //利用sizeof(T)的大小去宣告一個陣列,檢查是否可行,不可行說明是incomplete型別,不執行delete,這在編譯器就可以決斷
}
//使用該函式刪除scoped_ptr內部指標,重置為p指標。不過這不符合scoped_ptr意圖,該函式儘量不要用
void reset(T * p = 0) // never throws
{
BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
this_type(p).swap(*this);
}
T & operator*() const // never throws
{
BOOST_ASSERT( px != 0 );
return *px;
}
T * operator->() const // never throws
{
BOOST_ASSERT( px != 0 );
return px;
}
T * get() const BOOST_NOEXCEPT
{
return px;
}
// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp> //有關bool型別的運算子過載
//swap僅交換指標,這是對"pimpl"手法的優化
void swap(scoped_ptr & b) BOOST_NOEXCEPT
{
T * tmp = b.px;
b.px = px;
px = tmp;
}
};
//boost作用域內的swap,是一個no-member函式,供外部交換兩個scoped_ptr。不用std::swap的原因是std::swap不能優化"pimpl"手法。
template<class T> inline void swap(scoped_ptr<T> & a, scoped_ptr<T> & b) BOOST_NOEXCEPT
{
a.swap(b);
}
// get_pointer(p) is a generic way to say p.get()
//提供外部獲取指標的途徑
template<class T> inline T * get_pointer(scoped_ptr<T> const & p) BOOST_NOEXCEPT
{
return p.get();
}
} // namespace boost
解構函式中的check_delete是這樣的:
template<class T> inline void checked_delete(T * x)
{
// intentionally complex - simplification causes regressions
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete x;
}
其實就是按T的大小宣告一個陣列,如果該型別為不完全(incomplete)型別,這樣編譯會不通過。如僅僅宣告一個類,但沒有實現,直接用來做引數,這就是不完全型別。
關於operator bool的過載如下:
operator bool () const BOOST_NOEXCEPT
{
return px != 0;
}
這樣可以把該指標做來一個bool值來判斷。注意,operator bool過載不能有返回值。
操作函式
scoped_ptr的建構函式接受一個型別為T*的指標p,建立出一個scoped_ptr物件,並在內部儲存指標引數p。p必須是一個new表示式動態分配的結果,或者是個空指標。當scoped_ptr物件的生命期結束時,解構函式~scoped_ptr()會使用delete操作符自動銷燬鎖儲存的指標物件,從而正確地回收資源。
scoped_ptr同時把拷貝建構函式和賦值操縱符都宣告為私有的,禁止對只能指標的複製操作,保證了被它管理的指標不能被轉讓所有權。
成員函式reset()的功能是重置**scoped_ptr:它刪除原來儲存的指標,再儲存新的指標值p。如果p是空指標,那麼scoped_ptr將不持有任何指標。一般情況下reset()不應該被呼叫,因為它違背了scoped_ptr的本意——資源應該一直有scoped_ptr自己自動管理。
scoped_ptr用operator*()和operator->()過載瞭解引用操作符*和箭頭操作符->,以模仿被代理的原始指標的行為,因此可以把scoped_ptr物件如同指標一樣使用。如果scoped_ptr儲存空指標,那麼這兩個操作的行為未定義。
scoped_ptr不支援比較操作,不能在兩個scoped_ptr之間、scoped_ptr和原始指標或空指標之間進行相等或者不相等測試,我們也無法為它編寫額外的比較函式,因為它已經將operator==和operator!=兩個操作符過載都宣告為私有的。**但scoped_ptr提供了一個可以在bool語境中自動轉換成bool值(如if條件表示式)的功能。用來測試scoped_ptr是否持有一個有效的指標(非空)。它可以代替與空指標的比較操作,而且寫法更簡單。
成員函式swap()可以交換兩個scoped_ptr儲存的原始指標。它是高效的操作,被用於實現reset()函式,也可以被boost::swap所利用。
最後是成員函式get(),它返回scoped_ptr內部儲存的原始指標,可以用在某些要求必須是原始指標的場景(如底層的C介面)。但使用時必須小心,這將使原始指標脫離scoped_ptr的控制!不能對這個指標做delete操作,否則scoped_ptr析構時會對已經刪除的指標再進行刪除操作,發生未定義行為(core dump,呵呵)。
用法
下面是程式碼示例:
struct posix_file {
posix_file(const char* file_name)
{ cout<<"open file:"<<file_name<<endl; }
~posix_file()
{ cout<<"close file"<<endl; }
};
int main()
{
scoped_ptr<int> p(new int);
if(p){
*p = 100;
cout<<*p<<endl;
}
p.reset();
assert(p == 0);
if(!p){
cout<<"scoped_ptr == null"<<endl;
}
scoped_ptr<posix_file> fp(new posix_file("/tmp/a.txt"));
return 0;
}
這將輸出:
注意:scoped_ptr可沒有定義operator ++,等操作,不要亂用。並且scoped_ptr在作為成員變數時,所謂的作用域即類的內部作用域,所以會在解構函式的最後scoped_ptr才會析構。
與auto_ptr的區別
之前的這篇部落格分析了auto_ptr:auto_ptr原始碼分析,這次來說說scoped_ptr和auto_ptr的區別。
scoped_ptr的用法與auto_ptr幾乎於洋,大多數情況下它可以與auto_ptr相互替換,**它可以從一個auto_ptr獲得指標的管理權(同時auto_ptr失去管理權)。
scoped_ptr也具有auto_ptr同樣的”缺陷”——不能用作容器的元素,但原因不同:auto_ptr是因為它的轉移語義,而scoped_ptr則是因為不支援拷貝和賦值,不符合容器對元素型別的要求。
scoped_ptr和auto_ptr的根本性區別在於指標的所有權。auto_ptr被特意設計為指標的所有權是可轉移的,可以在函式間傳遞,同一時刻只能有一個auto_ptr管理指標。它的用意是好的,但轉移語義太過於微妙(一個拿走,一個就得失去),不熟悉auto_ptr特性的初學者容易引發錯誤。而scoped_ptr把拷貝建構函式和賦值函式都宣告為私有的,拒絕了指標所有權的轉讓——除了scoped_ptr自己,其他任何人都無權訪問被管理的指標,從而保證了指標的絕對安全。
比起auto_ptr,scoped_ptr更明確表明了程式碼原始編寫者的意圖:只能在定義的作用域內使用,不可轉讓,這在程式碼後續的維護生命週期中很重要。
參考:
- Boost程式庫完全開發指南,作者:羅劍鋒
相關文章
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】Vector原始碼剖析Java原始碼
- 【Java集合原始碼剖析】HashMap原始碼剖析Java原始碼HashMap
- 【Java集合原始碼剖析】Hashtable原始碼剖析Java原始碼
- 【Java集合原始碼剖析】TreeMap原始碼剖析Java原始碼
- 【Java集合原始碼剖析】LinkedList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】LinkedHashmap原始碼剖析Java原始碼HashMap
- epoll–原始碼剖析原始碼
- HashMap原始碼剖析HashMap原始碼
- Alamofire 原始碼剖析原始碼
- Handler原始碼剖析原始碼
- Kafka 原始碼剖析Kafka原始碼
- TreeMap原始碼剖析原始碼
- SDWebImage原始碼剖析(-)Web原始碼
- Boost原始碼剖析--原始碼
- Spring原始碼剖析9:Spring事務原始碼剖析Spring原始碼
- Flutter 原始碼剖析(一)Flutter原始碼
- 全面剖析 Redux 原始碼Redux原始碼
- vue原始碼剖析(一)Vue原始碼
- Kafka 原始碼剖析(一)Kafka原始碼
- Thread原始碼剖析thread原始碼
- Retrofit 原始碼剖析-深入原始碼
- SDWebImage原始碼剖析(二)Web原始碼
- iOS Aspects原始碼剖析iOS原始碼
- Apache Spark原始碼剖析ApacheSpark原始碼
- 《STL原始碼剖析》-- memory原始碼
- mmdetection原始碼剖析(1)--NMS原始碼
- Java LinkedList 原始碼剖析Java原始碼
- 深入剖析RocketMQ原始碼-NameServerMQ原始碼Server
- spark核心原始碼深度剖析Spark原始碼
- STL原始碼剖析——vector容器原始碼
- 深入剖析(JDK)ArrayQueue原始碼JDK原始碼
- 深入剖析LinkedList原始碼原始碼
- ThreadLocal原始碼深度剖析thread原始碼
- scoped_array原始碼剖析原始碼
- EMPI原始碼剖析(原創)原始碼
- Java集合:HashMap原始碼剖析JavaHashMap原始碼