2015年2月7日 星期六

關於RAII的探討(資源獲取與洩漏)

需要注意資源獲取(宣告)後是否在該區段內就free掉

    The RAII Idiom[1]

作者: gocpp (cpp) 看板: C_and_CPP
標題: [心得] The RAII Idiom
時間: Sun Aug 14 13:43:46 2005


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 函式庫[2][3]中的處理多緒程式的幾個 class,就是用這種
方式設計的。

有些 C++ 編譯器支援類似 Java 或 C# 的 try __finally 的功能,
就是為了解決在複雜程式迴路下,資源配置和釋放的問題。但其實
對 C++ 來說是不需要的,只要善用 RAII 就可以了。只要僅記「資
源配置即初始化」,這個基本原則,解構式就是最自然最方便的自
動化資源管理機制。



對於RAII的解說[4]

Reference:
[1]https://www.ptt.cc/man/C_and_CPP/D8D2/DA94/DDBB/M.1127480790.A.3B6.html
[2]http://darkranger.no-ip.org/content/loki-software-1998-2001
[3]http://blog.monkeypotion.net/gameprog/advanced/profiling-in-pooled-allocation-techniques
[4]http://www.cppblog.com/jinq0123/archive/2008/05/20/50522.aspx


沒有留言:

張貼留言