TAG

首都機能移轉 (2) 歌詞 (2) 靠北文 (40) 戲言 (30) 糟糕 (7) ACG (23) Assembly (2) Boost (2) C (31) C++ (69) CMake (4) CSIE (67) Debian (34) Design_Pattern (2) Django (1) Eclipse (1) en_US (13) FFmpeg (3) FoolproofProject (26) FreeBSD (2) Git (4) GNU_Linux (65) IDE (5) Java (11) JavaScript (19) KDE (15) Khopper (16) KomiX (3) Kubuntu (18) Life (1) Lighttpd (2) Mac_OS_X (2) Opera (1) PHP (2) PicKing (2) Programing (21) Prolog (1) Python (7) QSnapshot (2) Qt (30) Qt_Jambi (1) Regular_Expression (1) Shell_Script (7) Talk (98) VirtualBox (7) Visual_Studio (13) Windows (18) zh_TW (36)

2012年8月3日 星期五

Garbage Collection (2)

上一篇提到了 reference counting 以及 weak reference,這篇我將以 C++11 的 smart pointer 為骨幹做介紹,但核心概念都是差不多的。
C++11 引入了一系列的 smart pointer,使用 RAII (Resource Acquisition Is Initialization) 手法來確保資源在生命週期結束後的回收行為。想法非常簡單:配置在 stack 上的變數,在離開 scope 後一定會呼叫 destructor,因此只要設計一系列的容器,讓它們在 destructor 裡做回收行為,就可以讓編譯器為我們回收資源。C++11 的 smart pointer 實際上不只是自動化的 new/delete 容器,因為其 allocator/deleter 可自訂,programmer 實際上可用它來管理任何種類的資源。

Strong Reference

在 C++ 裡,std::shared_ptr 即是使用 strong reference,也是最常被用到的 smart pointer。用法如下:
std::shared_ptr< Object > sp( new Object );
sp.get(); // get raw pointer
sp.reset(); // ref count -1, set to null pointer
sp.reset( new Object ); // manage another pointer
std::shared_ptr< Object > sp2( sp ); // ref count +1
用同一個 pointer 建立兩個不同的 std::shared_ptr 是錯誤用法,會造成一次以上的回收:
Object o = new Object;
std::shared_ptr< Object > sp1( o );
std::shared_ptr< Object > sp2( o ); // bad

Weak Reference

在 C++11,weak reference 對應的 smart pointer 是 std::weak_ptr。前篇提到了 weak reference 可以用來解決 cyclic reference,那麼是怎麼做到的呢?很簡單,因為 weak reference 不參與 reference counting,也就是說它的生成,複製,銷毀都不會影響計數器,自然也不會做任何回收的動作。
那麼為什麼我們需要 weak reference,而不是簡單地用 raw pointer 呢?差別就在 weak reference 雖然不參與 reference counting,但它仍然知道該 pointer 目前的計數器資訊,這讓它可以確認資源是否已被回收,並在需要時暫時轉換為 strong reference。這在多緒環境時尤其重要:
void thread1() {
    // ...
    // sp is a std::shared_ptr
    sp.reset(); // (1)
    // ...
}

void thread2() {
    // ...
    // wp is a std::weak_ptr
    wp.get() // (2)
    // ...
}
如果 std::weak_ptr 跟 std::shared_ptr 一樣只用 get() 取得 raw pointer,那麼在上述的例子中,(1) 如果觸發了回收,那麼 (2) 就會出事了。因此 std::weak_ptr 必須要使用 lock() 函式取得一個 std::shared_ptr,讓計數器暫時不歸零,就可以避免這個狀況:
{
    std::shared_ptr< Object > sp = wp.lock();
    // counter will not be 0 in this scope
}

Strong Reference From This

考慮以下的二元樹節點實作:
class Node {
public:
    void setParent( std::weak_ptr< Node > n ) {
        this->p = n;
    }
    void setLeft( std::shared_ptr< Node > n ) {
        this->l = n;
        n->setParent( std::shared_ptr< Node >( this ) );
    }
    void setRight( std::shared_ptr< Node > n ) {
        this->r = n;
        n->setParent( std::shared_ptr< Node >( this ) );
    }
private:
    std::weak_ptr< Node > p;
    std::shared_ptr< Node > l, r;
};
以上的程式片段犯了大忌:直接使用 this 建立 shared_ptr,導致離開該函式後會讓 shared_ptr 歸零,this 被 delete。
但是我們的確需要知道目前正在管理 this 的 shared_ptr,這時我們需要使用 std::enable_shared_from_this。繼承自 enable_shared_from_this 的類別,可以使用 shared_from_this() 函式取得管理 this 的 shared_ptr。實作原理基本上是儲存一個 weak_ptr,外部建立 shared_ptr 時更新這個 weak_ptr,在呼叫 shared_from_this() 時再轉換回 shared_ptr。用法如下:
class Node: public std::enable_shared_from_this< Node > {
public:
    void setParent( std::weak_ptr< Node > n ) {
        this->p = n;
    }
    void setLeft( std::shared_ptr< Node > n ) {
        this->l = n;
        n->setParent( this->shared_from_this() );
    }
    void setRight( std::shared_ptr< Node > n ) {
        this->r = n;
        n->setParent( this->shared_from_this() );
    }
private:
    std::weak_ptr< Node > p;
    std::shared_ptr< Node > l, r;
};

Unique Reference

在特殊情況下,可能會需要確保同一時間只有一個物件有擁有權,這時候 std::unique_ptr 就派上用場了。unique_ptr 是 std::auto_ptr 的改良版本。auto_ptr 的複製語意是「摧毀式複製」,發生複製時會轉移資源的擁有權,這使得它無法被用於標準容器,並且在作為參數傳入函式時,會意外地被轉移擁有權,並在函式結束時觸發回收:
void f1() {
    std::auto_ptr< Object > a( new Object );
    // ownership moved into f2
    f2( a );
}

void f2( std::auto_ptr< Object > b ) {
    // destructs b and delete pointee
}
unique_ptr 改良了這點,它不具有 copy 語意,但具有 move 語意。需要複製時必須要明確使用 std::move 轉移擁有權。

沒有留言:

張貼留言