程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++慣用法之RAII

C++慣用法之RAII

編輯:關於C++

C++號稱是多范式的通用編程語言,但是RAII實際上已在C++編程技術中變成 不可或缺的核心技術。RAII幾乎無處不在的身影不僅僅來自於C++之父的大力提 倡,更來自於這一技術本身的簡單,高效和幾乎無所不能的適應面。

如 果您還沒有聽說過RAII的話,那麼我在這裡再重新敘述一遍,RAII是下列英文短 語的首字母縮寫:

Resource Acquisition Is Initialisation

這 句話直譯為中文的意思是: 資源獲得即初始化。這只是一個短語,不能指望靠 望文生義來了解字面背後的完整含義,但是短語本身的確反映了重要的論點: 資源是其一,初始化是其二。

RAII 是有關資源的。資源是一切需要分配 的數量有限的資料。比如,存儲器,文件句柄,網絡套接字端口,數據庫連接, 以及線程池等。基本上,由於物理的限制,所有的資料都是有限的。在某些特殊 的情況下,資料由於局部的極大豐富而喪失了資源的意義,比如沙子,空氣等。 但是在大多數情況下,資料都是有限的,需要我們善加管理。

資源管理 的最基本形式就是善始善終。申請了資源,用完了,就要歸還。在C++程序員生 活裡最常見的就是內存資源,資源管理就是內存管理: 申請了內存,不管什麼 時候邏輯上完成了對這片內存的使用,內存就要被正確地釋放。注意這裡的用詞 是"不管什麼時候". 在實際應用中,內存的使用邏輯是如此復雜,使 得邏輯上界定某塊內存的生命周期會成為非常繁瑣非常復雜的任務,而內存資源 就會在人類智力的疏漏中洩漏出去。而即使是簡單情況,內存也會在菜鳥程序員 手忙腳亂的拙劣中溜走。所以資源管理雖然可以簡化為一句"有始有終 ",在實際當中很難得到保證。

有 一類語言,比如Java,把內存資 源接管了,提供了所謂的自動內存管理,使用內存分配算法的方式為程序員模擬 了一個取之不盡用之不竭的准無窮內存模式。背後的思想是,在普通應用中,內 存的使用在空間和時間上都是相對集中的,這就允許用較少的內存來應付時間積 累上無限的內存請求。程序員使用這類語言就不用再考慮內存的釋放問題。負擔 就大大減輕了。

自動內存管理從原理上把內存資源倍增而產生一種資料 (准)無限的虛擬環境,從而把程序員從繁重的內存資源管理上解放出來,化更 多的精力考慮實際的事務代碼,提高了生產率。但是它也有自己的局限。一,自 動內存管理算法比較復雜,本身的程序就要占一定內存,同時自動內存管理用時 間換空間,還要求實際物理內存至少為應用最大瞬時所需內存的兩倍才能較好地 發揮作用,這一要求說明,自動內存管理其實已經不是在管理短缺意義上的 "資源",而是為不那麼浪費地使用豐富的資料提供一種說得過去的代 用方案。其次,由於自動內存管理是與具體的應用分離的,無法知道最合適的切 入點,所以自動內存管理的介入基本是不可預測的。這限制了自動內存管理在那 些對時間響應要求比較嚴格的程序中的應用。最後,自動內存管理僅是對內存資 源的管理,它無法管理其它的資源。除了內存,程序員往往要和其它的資源打交 道。自動內存管理模式無法應用到其它類型的資源管理。

C++提供了RAII 作為一個真正意義上的資源管理實用方案。這也是C++語言在資源管理這一意義 更加廣泛的問題上作出的貢獻。雖然其實用意義如此重大,但是其做法卻很簡單 ,就是用類來表示資源,在類的構造函數裡分配資源,在類的析構函數裡釋放資 源。比如,

class Resource {

public:

Resource(const char *name) : _resource(alloc_resource(name)) {……}

~Resource() { release_resource (_resource); }

};

資源類的使用也很簡單,按 泛圍使用。比如,有一個事務處理,使用到了某種資源。如果這一事務可以用一 個函數來表示,那麼,可以簡單地用一個在函數入口處分配的資源變量來表示資 源分配。例如:

void transaction1(const char *res_name)

{

Resource res(res_name);

// 後面 是使用資源res

}

不 管程序體內資源res的使用邏輯 如何復雜,退出路徑如何繁多,C++語言保證了在退出函數范圍的時候,資源對 象必定得到析構,資源必定得到釋放。這一保證甚至包括底層函數拋出異常的情 況。所有這些都是免費的,程序員要做的,就是用一個RAII語義的類來表達一類 資源,然後用一對標識代碼范圍的大括號來構勒資源的每一個生命周期范圍。如 果該事務邏輯過於復雜,無法有效地在單一函數裡表達,那麼可以用一個類來表 達該事務,這個類可以簡單地把用到的RAII資源作為成員包含,在表達邏輯上達 到了資源和事務邏輯共生死的地步。

你會說,這太不夠用了,資源的生 命周期可能是動態的,無法靜態決定。有時候甚至是外部用戶決定的。遇上這種 情況,智能指針類(其本身就有RAII語義)的引用計數基本上可以解決百分之九 十以上的問題。

例如在一個很實際的應用中,一個事務可能由用戶通過 界面發起執行,發起後可能由於外部資源失敗而中止,可能由於用戶命令而中止 ,也有可能是自然執行完畢而中止。

一 個比較自然的表達是線程池和事 務函數。接到用戶命令,主程序選擇可用的閒置線程,載入該事務函數運行,一 旦事務函數因為故障或者自然原因返回,線程重新回到閒置狀態。事務函數和主 程序用數據同步通訊的方式來實現事務運行狀態的控制。在這種方式中,資源成 為函數的一部分,其生死已基本不是我們關心的問題,我們只要關心,何時調用 事務函數。這已經是比較大比較接近事務邏輯的問題了。

如果不能使用 線程,或者認為多線程管理要比資源管理還要微妙還要邪惡,那麼就要寫所謂的 異步過程。整個的邏輯就是要在一個線程裡通過輪詢和動作切割的方式來實現事 務和事件的多道並行處理,這仍然可以通過寫一個異步事務類來表達,資源將作 為成員附著在該事務類上,而所有的異步事務類將作為資源被輪詢循環所在的函 數自動管理(這是必然的,主循環需要輪流執行當前正在執行的事務)。

可見,通過RAII程序員成功地把資源管理問題弱化,轉為如何表達事務 邏輯上。而這正是程序員的主要任務。也就是說,一旦資源被用RAII的形式封裝 起來,程序員就不再考慮資源洩漏問題,而考慮如何表達事務邏輯的問題,這個 代價並不算大。當然,程序員要堅持只用資源類的對象形式而不是顯式動態分配 的形式(也就是函數裡的普通變量或者事務類裡的普通成員,而不是任何new出 來的對象形式),否則所有的努力都白費了。這算是一點點代碼要求。並不難做 到。

和自動內存管理比較起來,RAII僅需少量的管理代碼(對類不對對 象),能普遍適用於各種資源對象的使用,時間上可以控制和預測。能為資源管 理提供一個統一的模式。RAII是自由的,它更多是靠程序員對規范的簡單遵守( 堅持使用對象而不是指針)來達到目的。我認為,程序員是需要遵守紀律的,特 別是那些好的紀律。

使用RAII應該成為C++程序員的基本習慣,這正如書 寫無錯高效代碼應該成為每個C++程序員的追求。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved