精華區beta C_and_CPP 關於我們 聯絡資訊
RAII 是什麼,或許 C++ 用了很久的人也沒聽過。不過即使沒聽過, 在程式中也應該早已大量應用 RAII 的手法了。 常有人問 C++ 與 C 有何不同,這個問題。簡單地說,C 語言支援的 是單純的程序導向(procedural based) 編程,而 C++ 除此之外還 支援 1.object-based 2.object-oriented 3.generic 這三種編程的觀念。學習 C++ 的時候,不要只侷限在這個 keyword 是 什麼意思,那個標準容器怎麼用(當然,這些還是要懂的),種種微末 細節。更重要的是,必須了解 C++ 所支援與 C 不同的設計概念,在接 觸一個新的語言機制或用法時,最好先了解它之所以「存在的目的」, 為什麼要用到這些手法,和舊的方法相比,「有什麼好處」,又「有什 麼限制」…等等,才是提高整體編程水平的捷徑。 本篇的主題 RAII,其實就是 C++ 所支援,而是 C 所缺乏的第一個主要 的概念: object-based(以物件為基礎的)編程。當然,object based 的範疇遠比 RAII 大的多,RAII 只是其中一部份,但卻是非常重要的一 部份。 RAII 指的是「Resource Acquisition Is Initialisation」,直接的意 思是:「資源獲得即初始化」。意即:一旦在程式中有「資源配置」的 行為,也就是一旦有「配置、釋放」的動作,就讓「配置」成為一個初 始化的動作,如此,釋放動作就變成自動的了(依物件的 scope 決定)。 簡單地說,RAII 就是善用 C++ class 的解構式(destroctor),來達成 資源自動管理的目的。簡單應用如下: void f() // 一、使用 auto_ptr 避免手動 delete { // 假設由於某種原因,TMemoryStream 必須以 new 的方式建立 std::auto_ptr<TMemoryStream> p(new TMemoryStream); ... if (...) { throw 1; } ... } // OK, 沒問題,一旦碰到右大括號,即使發生異常,p 也會正確被釋放。 void g() // 二、使用 vector 取代手動配置 array { int N; std::cin >> N; std::vector<int> v(N); ... if (...) { throw 1; } ... } // OK, 沒問題,即使發生異常,也不必操心 v 內部的記憶體管理 std::string g2() // 三、以回傳物件的方式,取代回傳函式內部 new 的物件的指標 { std::string s; ... return s; } // OK, 外部模組不必擔心忘記釋放記憶體的問題。 以上三個例子都很簡單,看起來沒什麼,但這就是 RAII 的基本精神, 再舉個多緒程式的例子,普通是像這樣寫: void f() // 未考慮「異常安全」的版本 { ... ThreadLock(...); ... // 在此區域內,不允許兩個執行緒同時進入。 ThreadUnlock(...); ... }; 以上的程式看起來沒問題,但要是在 Lock 和 Unlock 之間,程式發生 異常(而且被上層處理掉了),Unlock 就沒被執行到,就出大事了。因 此,最保險的方法是應用 RAII 的精神,把「鎖定」和「解除」的功能 封裝起來: struct Lock { Lock(...) { ThreadLock(...); } ~Lock() { ThreadUnlock(...); } ... }; 如此,以上的 f 函式就可以改寫如下: void f2() // RAII 版本 { ... { Lock lock(...); ... } // OK, 不管是否發生異常,lock 物件一旦至此就會被解構。 ... } 著名的 Loki 函式庫中的處理多緒程式的幾個 class,就是用這種 方式設計的。 有些 C++ 編譯器支援類似 Java 或 C# 的 try __finally 的功能, 就是為了解決在複雜程式迴路下,資源配置和釋放的問題。但其實 對 C++ 來說是不需要的,只要善用 RAII 就可以了。只要僅記「資 源配置即初始化」,這個基本原則,解構式就是最自然最方便的自 動化資源管理機制。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 59.120.214.120
renderer:推 gocpp 大大真是善知與善說者 222.156.10.167 08/14
godfat:推 ~~ 61.224.44.251 08/14