C++ 對象資源管理慣用法

來源:互聯網
上載者:User

原文連結http://blog.csdn.net/breakerzy/article/details/7593137

關於 C++ 對象資源管理的慣用法,note-to-self + keynote + idiom case + cross-reference 式筆記

keyword: RAII, deleter, Two-stage Initialization, pimpl, Reference Counting (RC), Copy on Write (COW), Smart Pointer (SP)

 

目錄

  • C 語言的資源管理方法
  • RAII
  • deleter
  • Two-stage Initialization
  • pimpl
  • Reference Counting
  • Smart Pointer
  • 參考書籍
C 語言的資源管理方法^

見 [CPP LANG] 14.4 Resource Management

e.g. Very Beginning

  1. void copy_file(const char* src, const char* dst)  
  2. {  
  3.     FILE* srcFile = fopen(src, "r");  
  4.     if (srcFile == NULL)  
  5.         goto _RET;  
  6.   
  7.     FILE* dstFile = fopen(src, "w");  
  8.     if (dstFile == NULL)  
  9.         goto _CLOSE_SRC;  
  10.   
  11.     // read source file, and transform it's content.  
  12.     if (HANDLE_FAILED)  
  13.         goto _CLOSE_DST;  
  14.   
  15.     // end processing  
  16. _CLOSE_DST:  
  17.     fclose(dstFile);  
  18. _CLOSE_SRC:  
  19.     fclose(srcFile);  
  20. _RET:  
  21.     return;  
  22. }  

引出 Resource Management 的基本要求:

  1. 離開時釋放, Release before returning.
  2. 按照資源的申請的相反順序釋放資源, Resources are released in the reverse order of their acquisition.

其它的資源釋放手法(不建議):

  1. do-while-break 式:教條地避免使用上述 goto 式的變形
  2. throw-catch 式:throw 內建類型,通常是 int, char*,效率低、代碼亂
RAII^

RAII (Resource Acquisition Is Initialization) 資源申請即初始化,是 C++ 資源管理的主流技術和基石

見 [CPP LANG] 14.4.1; [EFFECT CPP] Item 13, 14; wiki: RAII
見 Stack Unwinding 堆棧回退, wiki: Call Stack

注意:

  1. RAII 式類要求其組成部分(基類和成員)也是 RAII 式的,對於那些非 RAII 的部分需要手動管理資源(在解構函式中釋放),如:

    • stdlib 類大多是 RAII 式的,如 iostream, string, STL container
    • MFC 的某些類是 RAII 式的,如 CWnd, CGdiObject
    • COM 介面不是 RAII 式的,需手動調用 Release 方法,但可用 CComPtr 等 SP 封裝,使其成為 RAII
  2. 不要讓解構函式拋出異常,見 [CPP LANG] 14.4.7; [EFFECT CPP] Item 8

Sample:

  • class File: FILE 的淺封裝
  • class ScopedBuf: scoped 型緩衝區類 (Use vector and resize to expand buffer instead.)
deleter^

見 [EFFECT CPP] Item 14; [BOOST TUTORIAL] 3.4.8

deleter 刪除器:如果資源不限於記憶體配置型,則需要用一種靈活的、統一的方法指定資源的釋放操作,如 TR1/Boost 的 shared_ptr 的建構函式第二個參數指定 deleter

Sample:

  • a batch of deleters: 可配置 Check(檢查是已釋放)和 Zeroed(釋放後置零),釋放操作包括 delete, delete[], free, Release (COM), CloseHandle, fclose
Two-stage Initialization^

見 Two Stage Construction in C++ versus Initializing Constructors, RAII in C++, Google C++ Style Guide: Doing Work in Constructors 中文翻譯

Two-stage Initialization/Construction (abbr. 2-stage init) 兩階段初始化/構造:

  1. stage 1, 調用建構函式,初始化對象本體
  2. stage 2, 調用對象的 init 方法,初始化對象涉及的資源,這裡的 init 是形式名,例如 fstream::open, auto_ptr::reset, CWnd::Create 是 init 的具現名。CWnd::Create 的 2-stage 是強制的(MFC 風格),而 fstream 和 auto_ptr 可用 init,也可用建構函式

Why 2-stage init? 或者說它能帶來什麼好處:

  1. 可複用對象本體

  2. 對象數組初始化。因為沒有文法指定初始化數組時,每個單元該如何構造(POD 例外,可用 {} 初始化每個單元),只能統一地用預設建構函式初始化,然後對每個單元調用 init 初始化

    替代方法:用放置式 placement new + 建構函式代替 init,std::allocator::construct 使用這種手法,更多 placement new 見 [EFFECT CPP] Item 52; [MEFFECT CPP] Item 8

  3. 如何說明對象初始化後的狀態,如報告初始化過程的錯誤:

    1. init 方法可用傳回值表示。Google C++ Style 傾向使用這種方法:建構函式 (init stage 1) 只做簡單初始化,稱為 trivial init。真正有意義的 non-trivial init 在 init 方法中進行。這是因為 Google C++ Style 不建議使用異常系統

    2. 建構函式通常用拋出異常的方法。我一般用這種方法,而不用 2-stage init,我覺得 2-stage init fucked RAII。當然,當初始化錯誤的預期較高,並且是效率敏感處的大量對象初始化時,2-stage init 是優選

    3. 設定對象內的狀態原因變數,適用於建構函式和 init。不建議用設定對象狀態變數的方法 3,除非對象本身有較強的 state-driven 特點

More about 2-stage init:

  1. public init vs. private init

    只有 public init 是為了 2-stage init 的目的,而 private init 是另外一個東西,它多半是為了將多個重載的建構函式之公用部分抽成一個 init 函數以減少代碼

  2. init vs. re-init

    當使用者使用 init 時,其實際的語義是 re-init 嗎?即執行過程:

    1. 先判斷對象是否已指派資源,如果是,則需釋放
    2. 申請新的資源

    於是:RAII 讓資源管理變簡單,而使用 2-stage init 又讓事情變複雜

  3. init vs. ctor

    考慮兩種方法的效率 (pseudocode):

    1. 使用建構函式:

      1. // d 是 for 的 scoped 型對象, 下面情況都會銷毀 d  
      2. // 1. 條件退出 2. break 退出  
      3. // 3. 每次迭代結束 4. continue 結束本次迭代  
      4. for (int i = 0; i < NUM; i++) {  
      5.     Data d(dataFile);  
      6.     d.process();  
      7. }  
    2. 使用 init:

      1. // 設 init 是 re-init 式的  
      2. Data d;  
      3. for (int i = 0; i < NUM; i++) {  
      4.     d.init(dataFile);  
      5.     d.process();  
      6. }  

    init 賺不了什麼便宜,而使用建構函式的版本可籍由 拖延構造對象手法 獲利,見 [EFFECT CPP] Item 26

pimpl^

見 [EFFECT CPP] Item 14, 25, 29, 31

pimpl (Pointer to Implementation) 實質是實現 (Implementation) 和封裝 (Wrapper) 分離,Bjarne 習慣把 Implementation 叫做 表示 (Representation)

注意:

  1. 開銷:

    空間開銷變小:只有 pimpl 指標
    時間開銷變大:多瞭解引操作

  2. 施行拷貝 (assgin, copy ctor) 的方式:

    1. Shallow Copy 淺拷貝:僅拷貝 pimpl 指標,例如標準 SP:auto_ptr, share_ptr
      淺拷貝使 wrapper 具有引用語義 (Reference Semantics),如果想使淺拷貝具有值語義,可用 RC + COW (Copy on Write) 寫時複製手法

    2. Deep Copy 深拷貝:拷貝 pimpl 所指物
      深拷貝使 wrapper 具有值語義 (Value Semantics),又拷貝語義

Reference Counting^

見 [EFFECT CPP] Item 13, 14; [MEFFECT CPP] Item 17, 29

Reference Counting (RC) 引用計數

一般不用從零寫 RC 類,只要類中包含 RC 的組成部分即可,如利用 share_ptr 成員

Sample:

  • class Person: RC + COW 簡單樣本

  • class String: 改自 [CPP LANG] 11.12 A String Class

    技術:pimpl, RC, COW, Proxy class。Proxy class 用以區分 operator[] 的讀寫語義:R-value Usage vs. L-value Usage,見 [MEFFECT CPP] Item 17, 30

    功能:值語義的 string 類樣本,沒有模板化之 CharType、CharTraits 和 Allocator

    Bjarne: That done, we can throw away our exercises and use the standard library string. (Ch.20)

Smart Pointer^

見 [CPP LANG] 11.10 Dereferencing, 14.4.2 auto_ptr; [EFFECT CPP] Item 13, 14, 15, 17, 18; [MEFFECT CPP] Item 28; [BOOST TUTORIAL] 3.1~3.7

Smart Pointer (SP) 智能指標是一種 Delegation of Raw Pointer 機制,技術上等於:RAII + pimpl + Ownership Semantics 所有權語義

常用 SP 助記(來自 stdlib/Boost/TR1):

  1. _ptr vs. _array 尾碼

    ptr 型託管單體對象 (new),array 型託管對象數組 (new[])
    array 型有 [] 操作(不檢查下標範圍),沒有 *, -> 操作
    array 型可用 vector 或 ptr 型 + vector 代替

  2. scoped 型

    所有權:Monopolize 獨佔,又 Noncopyable
    注意:不能用於值語義之 STL 容器的元素

  3. auto 型

    所有權:Transfer 轉移,又 Distructive Copy 破壞性拷貝
    注意:不能用於值語義之 STL 容器的元素

  4. shared 型

    所有權:Share 共用,又 RCSP (Reference-Counting Smart Pointer) 引用計數型智能指標
    特點:支援 deleter, Cross-DLL, Raw Pointer 訪問
    注意:Cycles of References 環狀引用問題

    見 <boost/make_shared.hpp> 的 make_shared

  5. weak_ptr

    所有權:Observe 觀察
    特點:偽 SP,不獨立使用,而是配合 shared_ptr 使用以解決環狀引用問題

    見 <boost/enable_shared_from_this.hpp> 的 shared_from_this

  6. intrusive_ptr

    特點:size 和 raw pointer 相同,利用被託管對象已有的 RC 機制(侵入式)

  7. CComPtr, CComQIPtr

    ATL 中託管 COM 介面的 SP,均派生自 CComPtrBase,自動執行 COM 引用計數 AddRef 和 Release。CComQIPtr 使用 QueryInterface (QI) 根據指定 IID 查詢並獲得 COM 介面。

Sample:

  • class AutoPtr, AutoArr: 可指定上文所述 deleter 的 auto 型 SP
參考書籍^
    • [CPP LANG] "C++ Programming Language, Special Ed", Bjarne Stroustrup
    • [EFFECT CPP] "Effective C++, 3Ed", Scott Meyers
    • [MEFFECT CPP] "More Effective C++", Scott Meyers
    • [BOOST TUTORIAL]《Boost 程式庫完全開發指南》,羅劍鋒
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.