RAII是C++語言中常見習慣用法,全稱為“Resource Acquisition Is Initialization”意為資源擷取就是初始化。通常用來管理對象記憶體資源,已經比如檔案描述符、互斥鎖等資源。RAII基本原理就是使用局部對象管理資源,依賴于構造函數和析構函數的性質以及它們與異常處理的互動作用。我們考慮如下情況:
class A { ..... }; //某資源對象
A * create(){...} ; //建立動态對象A 并傳回指針
//考慮某處理函數f
void f()
{
A *pImp=create();
.....
delete pImp;
}
沒錯這代碼看起來沒啥問題,但是考慮中間"....."部分加入一些控制流程語句如:if else 語句若其中包含return 語言,則必須在return語句傳回之前都加上delete pImp;若分支語言很多則需要大量的delete語句。又例如:在"....."部分中包含其它函數調用導緻異常發生,使得後面delete pImp 語句無法執行,進而導緻記憶體洩漏。在這種情況下RAII技術就非常适合!如下:
void f()
{
std::shared_ptr<A> pImp(create()); //利用智能指針(基本原理就是利用RAII技術)
.....
}
上述代碼create傳回的資源被當做其管理者shared_ptr的初始值。不論控制流程如何離開區塊,在這之中若無發生shared_ptr對象拷貝、複制等(引用計數情況),一旦對象被銷毀(對象離開作用域)其析構函數自動會被調用,于是自動對其所指對象調用delete語句釋放資源。即使發生異常情況,我們依然無需多操心!
在考慮如下情況:
int go(){....} //傳回一個處理參數
void processGo(std::shared_ptr<A> pImp,int v){....}; //處理過程
假如我們使用如下函數調用寫法:
void processGo(std::shared_ptr<A> (new A),go());
代碼看起來也沒有啥問題,但是上述調用可能造成記憶體洩漏。分析如下:
調用函數processGo() 之前會做以下三件事:
1.調用go() 2.執行“new A” 3.調用shared_ptr構造函數。
C++ 編譯器 對于以上三件事會按照什麼次序完成呢?當然2、3 事件 ,一定是2事件優先于3事件。 但是1事件執行就比較任意了,若按照如下執行次序: 第一步: 執行new A 第二步:調用go() 第三步:調用 shared_ptr構造函數 。
萬一go函數的調用導緻異常,在此情況下new A 傳回的指針将會遺失,因為它尚未被置入shared_ptr内。進而發生洩漏。
避免這類問題發生也很簡單。就是寫成如下形式:
std::shared_ptr<A> pImp(new A); //單獨語句内以智能指針存儲 new 對象
processGo(pImp,go()); //這樣就不至于造成洩漏
本文參考于effective C++