亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

理解術語和概念的含義 - RAII(資源獲取是初始化)

理解術語和概念的含義 - RAII(資源獲取是初始化)

C++ C
慕桂英546537 2019-07-29 15:36:46
理解術語和概念的含義 - RAII(資源獲取是初始化)您能否請C ++開發人員詳細介紹RAII是什么,為什么重要,以及它是否與其他語言有任何關聯?我做知道一點點。我相信它代表“資源獲取是初始化”。但是,這個名稱并不符合我對RAII的理解(可能不正確):我得到的印象是RAII是一種初始化堆棧上對象的方式,當這些變量超出范圍時,析構函數會自動被稱為導致資源被清理。那么為什么不稱為“使用堆棧觸發清理”(UTSTTC :)?你怎么從那里到“RAII”?你怎么能在堆棧上創建一些東西來清理堆上的東西呢?此外,是否有不能使用RAII的情況?你有沒有發現自己希望收集垃圾?至少一個垃圾收集器,你可以使用一些對象,同時讓其他人管理?謝謝。
查看完整描述

3 回答

?
慕桂英3389331

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解決方案。隨著時間的推移,我已經解決了這里記錄的方法。


查看完整回答
反對 回復 2019-07-29
?
慕容708150

TA貢獻1831條經驗 獲得超4個贊

那里有很好的答案,所以我只是添加了一些被遺忘的東西。

0. RAII是關于范圍的

RAII是關于兩者:

  1. 獲取構造函數中的資源(無論什么資源),并在析構函數中取消它。

  2. 在聲明變量時執行構造函數,并在變量超出范圍時自動執行析構函數。

其他人已經回答了這個問題,所以我不會詳細說明。

1.使用Java或C#編碼時,您已使用RAII ...

MONSIEUR JOURDAIN:什么!當我說,“妮可,把我的拖鞋帶給我,給我睡帽,”這是散文?

哲學碩士:是的,先生。

MONSIEUR JOURDAIN:四十多年來,我一直在講述散文而不知道任何事情,我非常感謝你教我這個。

- 莫里哀:中產階級紳士,第2幕,場景4

正如Jourdain先生用散文所做的那樣,C#甚至Java人已經使用RAII,但卻是隱藏的方式。例如,下面的Java代碼(這是通過替換在C#編寫的相同方式synchronizedlock):

void foo(){
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.}

...已經在使用RAII:互斥鎖獲取在關鍵字(synchronizedlock)中完成,取消將在退出范圍時完成。

它的符號非常自然,即使是從未聽說過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可以做很多事情......


查看完整回答
反對 回復 2019-07-29
  • 3 回答
  • 0 關注
  • 675 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號