3 回答

TA貢獻2036條經驗 獲得超8個贊
那么為什么不稱為“使用堆棧觸發清理”(UTSTTC :)?
RAII告訴你該怎么做:在構造函數中獲取你的資源!我會添加:一個資源,一個構造函數。UTSTTC只是其中的一個應用,RAII更多。
資源管理很糟糕。在這里,資源是在使用后需要清理的任何東西。對許多平臺上的項目進行的研究表明,大多數錯誤都與資源管理有關 - 而且在Windows上尤其糟糕(由于有許多類型的對象和分配器)。
在C ++中,由于異常和(C ++樣式)模板的組合,資源管理特別復雜。如需了解引擎蓋,請參閱GOTW8)。
C ++保證當且僅當構造函數成功時才調用析構函數。依靠這一點,RAII可以解決普通程序員可能甚至不知道的許多令人討厭的問題。除了“每當我返回時我的局部變量將被銷毀”之外,還有一些例子。
讓我們從FileHandle
使用RAII 的過于簡單化的課程開始:
class FileHandle{ FILE* file;public: explicit FileHandle(const char* name) { file = fopen(name); if (!file) { throw "MAYDAY! MAYDAY"; } } ~FileHandle() { // The only reason we are checking the file pointer for validity // is because it might have been moved (see below). // It is NOT needed to check against a failed constructor, // because the destructor is NEVER executed when the constructor fails! if (file) { fclose(file); } } // The following technicalities can be skipped on the first read. // They are not crucial to understanding the basic idea of RAII. // However, if you plan to implement your own RAII classes, // it is absolutely essential that you read on :) // It does not make sense to copy a file handle, // hence we disallow the otherwise implicitly generated copy operations. FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; // The following operations enable transfer of ownership // and require compiler support for rvalue references, a C++0x feature. // Essentially, a resource is "moved" from one object to another. FileHandle(FileHandle&& that) { file = that.file; that.file = 0; } FileHandle& operator=(FileHandle&& that) { file = that.file; that.file = 0; return *this; }}
如果構造失?。ㄓ欣猓?,則不會調用其他成員函數 - 甚至是析構函數。
RAII避免在無效狀態下使用對象。在我們使用對象之前,它已經讓生活更輕松。
現在,讓我們看看臨時對象:
void CopyFileData(FileHandle source, FileHandle dest);void Foo(){ CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));}
要處理三種錯誤情況:無法打開文件,只能打開一個文件,可以打開這兩個文件但復制文件失敗。在非RAII實現中,Foo
必須明確處理所有三種情況。
即使在一個聲明中獲得多個資源,RAII也會釋放已獲取的資源。
現在,讓我們聚合一些對象:
class Logger{ FileHandle original, duplex; // this logger can write to two files at once!public: Logger(const char* filename1, const char* filename2) : original(filename1), duplex(filename2) { if (!filewrite_duplex(original, duplex, "New Session")) throw "Ugh damn!"; }}
的構造Logger
將失敗original
的構造失?。ㄒ驗?code>filename1無法打開)duplex
的構造失?。ㄒ驗?code>filename2無法打開),或內寫入文件Logger
的構造體失敗。在任何這些情況下,Logger
都不會調用析構函數- 所以我們不能依賴Logger
析構函數來釋放文件。但是如果original
被構造,它的析構函數將在Logger
構造函數的清理期間被調用。
RAII簡化了部分施工后的清理工作。
否定點:
否定點?使用RAII和智能指針可以解決所有問題;-)
當您需要延遲獲取時,RAII有時會變得難以處理,將聚合對象推送到堆上。
想象一下Logger需要一個SetTargetFile(const char* target)
。在這種情況下,仍然需要成為其成員的句柄Logger
需要駐留在堆上(例如,在智能指針中,以適當地觸發句柄的破壞。)
我真的不希望收集垃圾。當我做C#時,我有時會感到一陣幸福,我不需要關心,但更多的是我想念所有可以通過確定性破壞創造的酷玩具。(使用IDisposable
只是不削減它。)
我有一個特別復雜的結構可能從GC中獲益,其中“簡單”智能指針會導致多個類的循環引用。我們通過仔細平衡強弱指針而陷入困境,但無論何時我們想要改變某些東西,我們都必須研究一個大關系圖。GC可能會更好,但是一些組件擁有應該盡快發布的資源。
關于FileHandle示例的注釋:它不是完整的,只是一個示例 - 但結果不正確。感謝Johannes Schaub指出并將FredOverflow轉變為正確的C ++ 0x解決方案。隨著時間的推移,我已經解決了這里記錄的方法。

TA貢獻1831條經驗 獲得超4個贊
那里有很好的答案,所以我只是添加了一些被遺忘的東西。
0. RAII是關于范圍的
RAII是關于兩者:
獲取構造函數中的資源(無論什么資源),并在析構函數中取消它。
在聲明變量時執行構造函數,并在變量超出范圍時自動執行析構函數。
其他人已經回答了這個問題,所以我不會詳細說明。
1.使用Java或C#編碼時,您已使用RAII ...
MONSIEUR JOURDAIN:什么!當我說,“妮可,把我的拖鞋帶給我,給我睡帽,”這是散文?
哲學碩士:是的,先生。
MONSIEUR JOURDAIN:四十多年來,我一直在講述散文而不知道任何事情,我非常感謝你教我這個。
- 莫里哀:中產階級紳士,第2幕,場景4
正如Jourdain先生用散文所做的那樣,C#甚至Java人已經使用RAII,但卻是隱藏的方式。例如,下面的Java代碼(這是通過替換在C#編寫的相同方式synchronized
用lock
):
void foo(){ // etc. synchronized(someObject) { // if something throws here, the lock on someObject will // be unlocked } // etc.}
...已經在使用RAII:互斥鎖獲取在關鍵字(synchronized
或lock
)中完成,取消將在退出范圍時完成。
它的符號非常自然,即使是從未聽說過RAII的人也幾乎不需要解釋。
C ++在Java和C#方面的優勢在于可以使用RAII進行任何操作。例如,有沒有直接內建等效的synchronized
,也沒有lock
在C ++中,但我們仍然可以擁有它們。
在C ++中,它將寫成:
void foo(){ // etc. { Lock lock(someObject) ; // lock is an object of type Lock whose // constructor acquires a mutex on // someObject and whose destructor will // un-acquire it // if something throws here, the lock on someObject will // be unlocked } // etc.}
這可以很容易地用Java / C#方式編寫(使用C ++宏):
void foo(){ // etc. LOCK(someObject) { // if something throws here, the lock on someObject will // be unlocked } // etc.}
2. RAII有其他用途
白兔:[唱歌]我遲到/我遲到/非常重要的約會。/沒時間說“你好?!?nbsp;/ 再見。/我遲到了,我遲到了,我遲到了。
- 愛麗絲夢游仙境(迪士尼版,1951年)
你知道什么時候會調用構造函數(在對象聲明中),并且你知道何時會調用它相應的析構函數(在作用域的出口處),所以你可以用一行來編寫幾乎神奇的代碼。歡迎來到C ++仙境(至少從C ++開發人員的角度來看)。
例如,您可以編寫一個計數器對象(我將其作為練習)并僅通過聲明其變量來使用它,就像上面使用的鎖對象一樣:
void foo(){ double timeElapsed = 0 ; { Counter counter(timeElapsed) ; // do something lengthy } // now, the timeElapsed variable contain the time elapsed // from the Counter's declaration till the scope exit}
當然,可以使用宏來編寫Java / C#方式:
void foo(){ double timeElapsed = 0 ; COUNTER(timeElapsed) { // do something lengthy } // now, the timeElapsed variable contain the time elapsed // from the Counter's declaration till the scope exit}
3.為什么C ++缺乏finally
?
[SHOUTING]這是最后的倒計時!
- 歐洲:最后的倒計時(抱歉,我沒有引號,這里...... :-)
該finally
子句在C#/ Java中用于處理范圍退出時的資源處理(通過return
拋出異常或拋出異常)。
精明的規范讀者會注意到C ++沒有finally子句。這不是錯誤,因為C ++不需要它,因為RAII已經處理了資源處理。(相信我,編寫C ++析構函數比編寫正確的Java finally子句,甚至是C#的正確Dispose方法更容易)。
不過,有時,一個finally
條款會很酷。我們可以用C ++做嗎?我們可以!再次使用RAII。
結論:RAII不僅僅是C ++中的哲學:它是C ++
RAII?這是C ++ !!!
- C ++開發人員憤怒的評論,被一位不起眼的斯巴達國王和他的300個朋友無恥地復制
當您在C ++中達到某種程度的經驗時,就開發人員和析構函數自動執行而言,您開始考慮RAII。
你開始在思維范圍,以及{
和}
人物成為在代碼中最重要的人。
幾乎所有東西都適合RAII:異常安全,互斥,數據庫連接,數據庫請求,服務器連接,時鐘,操作系統句柄等,以及最后但并非最不重要的內存。
數據庫部分是不可忽略的,因為,如果您接受支付價格,您甚至可以用“ 事務編程 ”方式編寫,執行代碼行和代碼行,直到最終確定是否要提交所有更改,或者,如果不可能,將所有更改都還原(只要每行至少滿足強異常保證)。(參見Herb的Sutter關于事務編程的文章的第二部分)。
就像拼圖一樣,一切都很合適。
RAII是C ++的重要組成部分,如果沒有它,C ++就不可能是C ++。
這解釋了為什么有經驗的C ++開發人員如此迷戀RAII,以及為什么RAII是他們在嘗試使用其他語言時首先搜索的內容。
它解釋了為什么垃圾收集器本身就是一項非常出色的技術,從C ++開發人員的角度來看并不那么令人印象深刻:
RAII已經處理了GC處理的大多數案例
GC在純托管對象上使用循環引用比RAII更好(通過智能使用弱指針緩解)
GC仍然限于內存,而RAII可以處理任何類型的資源。
如上所述,RAII可以做很多事情......

TA貢獻1836條經驗 獲得超5個贊
- 3 回答
- 0 關注
- 675 瀏覽
添加回答
舉報