2011-12-19 111 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

相关问题