scoped_ptr原始碼剖析

FreeeLinux發表於2017-01-20

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程式庫完全開發指南,作者:羅劍鋒

相關文章