C++中的RAII介紹


摘要

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM