2011-12-19 44 views
1

我想在RAII的幫助下實現一個類。這些資源應該在構造函數中獲得,但是可能失敗。我將在下面使用文件舉個例子:如何在資源獲取失敗的情況下實現RAII

class file { 
public: 
    file(const char* filename) { 
     file_ = fopen(filename, "w+"); 
     if(!file_) { 
      // Okay 
     } 
     else { 
      // ERROR 
     } 
    } 

    ~file() { 
     if (fclose(file_)) { 
      // ERROR 
     } 
    } 

    void write(const char* str) { 
     if (EOF == fputs(str, file_)) { 
      throw runtime_error("file write failure"); 
     } 
    } 
private: 
    FILE* file_; 
}; 

那麼,有什麼處理,如果FOPEN返回null,其發生錯誤的最好方法?因爲它是構造函數,所以我也不能返回NULL。

我希望有人能給我一個提示如何處理這樣的錯誤!

謝謝你,最好的問候,

閃光

回答

6

構造可以報告故障是通過拋出異常的唯一途徑。

相反,析構函數不得拋出異常(如果析構函數在堆棧展開期間拋出,則調用std::terminate,這會默認結束程序)。

如果破壞失敗,您可以

  • 燕子錯誤默默
  • 中止程序
  • 記錄錯誤和做上述其中之一。

如果您正確使用RAII,例外可以遍歷您的代碼而不會損壞。

這裏舉例:

#include <cerrno> 
#include <cstring> 
#include <sstream> 

file::file(const char* filename) 
{ 
    file_ = fopen(filename, "w+"); 

    if (!file_) 
    { 
     std::ostringstream os; 
     os << "Cannot open " << filename << ": " 
      << std::strerror(errno); 

     throw std::runtime_error(os.str()); 
    } 
} 

file::~file() 
{ 
    fclose(file_); 
} 

注意,該代碼有很多bug:在fclose功能可能會失敗,並且失敗可能會或可能不會進行相關的關閉(例如,一些寫錯誤僅報告時。在POSIX系統上調用close系統調用)。請將iostreams用於C++中的文件I/O,因爲它們提供了對這些問題的方便抽象。

+1

此外,析構函數應該*不*拋出。 – GManNickG 2011-12-19 18:10:06

+0

「如果堆棧展開期間析構函數拋出會發生什麼情況?」 - 調用'std :: terminate()'。因此從析構函數中拋出與調用'abort()'差不多。 – 2011-12-19 18:36:14

+0

@SteveJessop:謝謝,答案改進了。 – 2011-12-19 18:37:22

1

儘管名稱,RAII不一定涉及資源收購;例如, std::shared_ptr不會執行new。歷史名稱爲 ,並且該模式確實涉及清理。

File的情況下,當然是「正確」的答案是使用 std::ifstreamstd::ofstream,並用它做。 非常重要(至少對於輸出):RAII只能用於例外 清理,因爲您必須驗證close已正確 在正常情況下離開塊之前已完成。作爲一般 規則,如果接近失敗,你要刪除的文件(從main返回 EXIT_FAILURE,或做一些事情,這將使錯誤 明顯,並防止進一步的代碼從 數據是在假設執行書面)。因此 推遲close爲析構函數的唯一時間是在您已經發現 發現錯誤並且將要刪除該文件作爲清理 的一部分的情況下。

一般來說,輸出遵循事務模型,而不是RAII; I 傾向於將我的std::ofstream封裝在OutputFile類中,使用 commit()函數關閉該文件,並在該關閉成功時(且僅當)關閉成功時將該類標記爲 ;如果析構函數調用了 且文件尚未提交,則析構函數關閉 文件,然後將其刪除。 (假設某些更高級別會捕獲 例外,並將其轉換爲return EXIT_FAILURE的主數據。)

此外,IO在總體上有點特殊。通常,如果你不能創建一個對象,無論出於何種原因,構造函數應該引發一個異常;你不希望「殭屍」物體在周圍漂浮,而不能使用 。然而,在IO的情況下,您必須處理事實 該對象在施工後可能變得無效並且不可用,甚至在施工成功時 。由於無論如何你必須經常檢查它們的狀態(在每次讀入輸入之後,以及在輸出結束之後), 通常只需在對象 中設置一些內部標誌即可。

+0

「然後刪除它」 - 當然這個動作取決於文件的使用方式 - 如果它被用作日誌文件,那麼在失敗時刪除可能不會很有幫助。但是,如果您正在將文件的新版本寫入臨時名稱,準備通過原始方式將其重命名爲原始文件,那麼刪除就是在失敗時執行的操作。 – 2011-12-19 18:46:04

+0

@SteveJessop是的。日誌文件是一個特例,我同意;我通常懶得去檢查他們的錯誤狀態 - 他們是「盡力而爲」的。然而,在大多數情況下,最好不要留下不完整的文件。同樣,爲了將錯誤狀態傳播到返回碼,你需要'close'或'flush'std :: cout,但通常情況下,'std :: cerr'應該是「盡力而爲」。 (請注意,'cmd orig> tmp && mv tmp orig'是Unix下常見的習慣用法,如果寫入失敗不會導致錯誤返回狀態,會產生不愉快的後果。) – 2011-12-20 10:20:57

相關問題