摘要
RAII技術被認為是C++中管理資源的最佳方法,進一步引申,使用RAII技術也可以實現安全、簡潔的狀態管理,編寫出優雅的異常安全的代碼。
資源管理
RAII是C++的發明者Bjarne Stroustrup提出的概念,RAII全稱是“Resource Acquisition is Initialization”,直譯過來是“資源獲取即初始化”,也就是說在構造函數中申請分配資源,在析構函數中釋放資源。因為C++的語言機制保證了,當一個對象創建的時候,自動調用構造函數,當對象超出作用域的時候會自動調用析構函數。所以,在RAII的指導下,我們應該使用類來管理資源,將資源和對象的生命周期綁定。
智能指針(std::shared_ptr和std::unique_ptr)即RAII最具代表的實現,使用智能指針,可以實現自動的內存管理,再也不需要擔心忘記delete造成的內存泄漏。毫不誇張的來講,有了智能指針,代碼中幾乎不需要再出現delete了。
內存只是資源的一種,在這里我們討論一下更加廣義的資源管理。比如說文件的打開與關閉、windows中句柄的獲取與釋放等等。按照常規的RAII技術需要寫一堆管理它們的類,有的時候顯得比較麻煩。但是如果手動釋放,通常還要考慮各種異常處理,比如說:
void function() { FILE *f = fopen("test.txt", 'r'); if (.....) { fclose(f); return; } else if(.....) { fclose(f); return; } fclose(f); ...... }
這里介紹一個網上實現的我認為比較簡潔的方法,文章在這里。作者使用了C++11標准中的lambda表達式和std::function相結合的方法,非常簡潔、明了。直接看代碼吧:
#define SCOPEGUARD_LINENAME_CAT(name, line) name##line #define SCOPEGUARD_LINENAME(name, line) SCOPEGUARD_LINENAME_CAT(name, line) #define ON_SCOPE_EXIT(callback) ScopeGuard SCOPEGUARD_LINENAME(EXIT, __LINE__)(callback) class ScopeGuard { public: explicit ScopeGuard(std::function<void()> f) : handle_exit_scope_(f){}; ~ScopeGuard(){ handle_exit_scope_(); } private: std::function<void()> handle_exit_scope_; }; int main() { { A *a = new A(); ON_SCOPE_EXIT([&] {delete a; }); ...... } { std::ofstream f("test.txt"); ON_SCOPE_EXIT([&] {f.close(); }); ...... } system("pause"); return 0; }
作者為了使用方便,還定義了根據行號來對ScopeGuard類型對象命名的宏定義。看到了吧,當ScopeGuard對象超出作用域,ScopeGuard的析構函數中會調用handle_exit_scope_函數,也就是lambda表達式中的內容,所以在lamabda表達式中填上資源釋放的代碼即可,多么簡潔、明了。既不需要為每種資源管理單獨寫對應的管理類,也不需要考慮手動釋放出現各種異常情況下的處理,同時資源的申請和釋放放在一起去寫,永遠不會忘記。
狀態管理
RAII另一個引申的應用是可以實現安全的狀態管理。一個典型的應用就是在線程同步中,使用std::unique_lock或者std::lock_guard對互斥量std:: mutex進行狀態管理。通常我們不會寫出如下的代碼:
std::mutex mutex_; void function() { mutex_.lock(); ...... ...... mutex_.unlock(); }
因為,在互斥量lock和unlock之間的代碼很可能會出現異常,或者有return語句,這樣的話,互斥量就不會正確的unlock,會導致線程的死鎖。所以正確的方式是使用std::unique_lock或者std::lock_guard對互斥量進行狀態管理:
std::mutex mutex_; void function() { std::lock_guard<std::mutex> lock(mutex_); ...... ...... }
在創建std::lock_guard對象的時候,會對std::mutex對象進行lock,當std::lock_guard對象在超出作用域時,會自動std::mutex對象進行解鎖,這樣的話,就不用擔心代碼異常造成的線程死鎖。
總結
通過上面的分析可以看出,RAII的核心思想是將資源或者狀態與對象的生命周期綁定,通過C++的語言機制,實現資源和狀態的安全管理。理解和使用RAII能使軟件設計更清晰,代碼更健壯。
參考
1、http://mindhacks.cn/2012/08/27/modern-cpp-practices/
2、http://www.jellythink.com/archives/101
3、http://www.cppblog.com/aaxron/archive/2011/03/22/142475.html