Effective C++ 學習筆記 第三章:資源管理

第一章見 Effective C++ 學習筆記 第一章:讓自己習慣 C++
第二章見 Effective C++ 學習筆記 第二章:構造、析構、賦值運算

計算機資源,除了我們熟悉的內存,還包括其他需要在使用時佔用,在使用後歸還給系統的東西,還包括如文件描述符、互斥鎖、圖形控件、數據庫連接、網絡端口等。

條款 13: 以對象管理資源

Use objects to manage resources.

話題 1:不要讓調用者回收資源

不要把 delete 的工作單獨留給調用者。有些時候是擔心調用者忘記 delete,但更隱晦的情況可能是調用程序寫了 delete,但因爲一些原因,不會執行到 delete,比如之前的一些異常,或者隱藏很深的 return 語句。程序在維護過程中,逐漸可能會暴露這種問題。
我們應該將資源管理的工作放到對象內,讓對象的析構函數來完成資源回收的任務。

話題 2:智能指針

可以將資源賦給智能指針來引用,智能指針可以自動在退出作用域時,回收它指向的資源。
比如 auto_ptr:

class Inv();   // 省略定義
Inv* createInv();  // 工廠函數,返回 Inv 的對象

void f() {
    std::auto_ptr<Inv> pInv(createInv());
    // 退出 f() 作用域時,Inv 中的資源會被自動回收
}

兩個關鍵工作:

  • 獲得資源後立即將其放入對象內管理。比如上邊代碼放到了 auto_ptr pInv 中。 這叫 RAII,即資源取得時便是初始化時(Resource Acquisition Is Initialization)。
  • 管理對象運用析構函數確保資源被釋放。比如上邊代碼中,auto_ptr 的析構函數會釋放資源。因爲我們要求析構函數中不得拋出異常,所以一旦管理對象要被析構了,它所管理的資源也一定會被釋放。

別讓多個 auto_ptr 指針指向同一個對象,如果其中一個 auto_ptr 已經析構了,那其他 auto_ptr 將指向已經被釋放的資源位置!auto_ptr 有個特殊的性質,它的 copy 構造函數和copy 運算符重載,是會控制唯一性,它們會讓 copy 的指針爲 null:

std::auto_ptr<Inv> pInv1 (createInv());   // pInv1 指向 Inv 資源
std::auto_ptr<Inv> pInv2 (pInv1);         // 現在 pInv2 指向 Inv 資源,pInv1 爲 null
pInv1 = pInv2;                            // 現在 pInv1 指向 Inv 資源,pInv2 爲 null

對這個問題的替代方案是 shared_ptr,它是引用計數型智能指針。類似於互斥鎖和共享鎖的概念,當多個 shared_ptr 指向同一個資源時,它可計數,當計數不爲 0 時,析構個別 shared_ptr 不會釋放資源,只有當計數爲 0 時,纔會析構這些 shared_ptr 指向的資源。
但 shared_ptr 不能解決環狀引用,也就是兩個指針互相指向對方,所以它只是類似於資源回收。
注意,shared_ptr 是 tr1 裏邊提供的,它的名字空間是 std::tr1::shared_ptr。

auto_ptr 和 shared_ptr 只是 RAII 的一個典型應用,我們自己寫的結構中也可以應用 RAII。

話題 3:智能指針不能用來指向數組

auto_ptr 和 shared_ptr 在釋放資源時都是用 delete,而不是 delete[],所以它們不應該被用來指向數組結構,而且,編譯器也無法檢查這種錯誤,一旦指向了數組結構,那最後在析構時,就無法完全釋放資源。
作者的理由是,C++ 中總有辦法取代數組,比如 string 和 vector。不過,Boost 庫中的 boost::scopted_array 和 boost::shared_array 可以實現指向數組的操作。

其實,createInv() 這個接口本身設計的有問題,它不應該把一個資源直接扔出來。

原書建議

  • 爲了防止資源泄漏,請使用 RAII 對象,它們在構造函數中獲取資源並在析構函數中釋放資源。
  • 兩個常被使用的 RAII 對象是 tr1::shared_ptr 和 auto_ptr,前者通常更常用,區別在於 copy 時是否允許多個指針指向同一個資源對象。

條款 14 :在資源管理類中小心 copying 行爲

Think carefully about copying behavior in resource-managing classes.

  1. 禁止資源管理類對象的複製,這種操作會導致意外情況。使用 private Uncopyable 方式來處理,見 條款6
  2. 或者,使用引用計數,這和 tr1::shared_ptr 是類似的境況。shared_ptr 支持定義刪除器(deleter),默認情況下,auto_ptr 和 shared_ptr 都是通過 delete 來釋放資源,但有些時候,我們希望通過其他方式釋放資源,比如 unlock(mutex)。
  3. 複製資源管理類時,進行深度拷貝。也就是並不是複製類對象本身,而是複製類對象管理的所有資源,都形成副本。比如 string 的複製,雖然 string 的內容是指向 heap 上字符內容的指針,但複製 string 對象時, heap 上的字符內容都會產生副本。
  4. 控制任何時刻,只允許最多 1 個資源管理類對象來管理資源,就像 auto_ptr 那樣子。通過重載兩個 copy 函數來實現。

原書建議

  • 複製 RAII 對象必須一併複製它所管理的資源,所以資源的 copying 行爲決定 RAII 對象的 copying 行爲。
  • 普遍而常見的 RAII classes copying 行爲是:禁止 copying、引用計數法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章