如何正确地在fstream输出操作中进行“捕获所有”错误检查?

3
什么是在向fstream发送数据时检查一般错误的正确方法?
更新:我主要关心的是关于输出和任何数据实际写入硬盘之间存在延迟的一些事情。我的假设是命令“save_file_obj << save_str”只会将数据发送到某种缓冲区,并且以下检查“if (save_file_obj.bad())”无法确定是否存在操作系统或硬件问题。我只想知道将字符串发送到文件并检查确保它已写入磁盘的明确的“全捕获”方法,然后进行任何后续操作,例如关闭程序。
我有以下代码...
int Saver::output()
{
    save_file_handle.open(file_name.c_str());
    if (save_file_handle.is_open())
    {
        save_file_handle << save_str.c_str();

        if (save_file_handle.bad())
        {
            x_message("Error - failed to save file");
            return 0;
        }

        save_file_handle.close();

        if (save_file_handle.bad())
        {
            x_message("Error - failed to save file");
            return 0;
        }

        return 1;
    }
    else
    {
        x_message("Error - couldn't open save file");
        return 0;
    }
} 
3个回答

5
一些要点。首先:
save_file_handle

“fstream”不是一个好的C++文件实例的名称。fstreams并不是文件句柄,这样做只会让读者感到困惑。

其次,正如Michael所指出的那样,没有必要将C++字符串转换为C字符串。你唯一应该发现自己这样做的时候是在与C风格的API进行交互时,以及使用一些设计不良的C++ API,比如(不幸的是)fstream :: open()。

第三,测试流操作是否成功的规范方式是测试操作本身。流具有转换为void *的功能,这意味着你可以编写类似于以下内容的代码:

if ( save_file_handle << save_str ) {
   // operation worked
}
else {
   // failed for some reason
}

你的代码应该始终测试流操作,无论是输入还是输出。


我认为你的意思是转换为bool,而不是void*。 - Michael Aaron Safyan
@Michael 我的意思是我所说的 - 标准规定了转换为 void *。 - anon
2
@Neil,没事,你是对的... 我一直认为它是 bool(因为 void* 隐式转换为 bool)。我从来没有意识到它实际上是 void。很有趣,你知道他们为什么选择了这个约定吗?人们可以用 void 对象做什么呢? - Michael Aaron Safyan
那个“其次”的部分不必要地重复了迈克尔的回答,现在也需要它的反驳被不必要地重复。;) 那个建议,在 ~99% 的情况下是正确的,但在 OP 的情况下却是错误的:请看关于不使用 string 而是使用 basic_string<unsigned char> 的评论。非常好的指出了问题的重要部分。 - Sz.

4
除了关闭后的检查外,其他都很合理。不过,我会稍微重构一下并抛出异常或使用bool,但这只是个人偏好:
bool Saver::output()
{
    std::fstream out(_filename.c_str(),std::ios::out);
    if ( ! out.is_open() ){
         LOG4CXX_ERROR(_logger,"无法打开 \""<<filename<<"\"");
         return false;
    }
out << _savestr << std::endl; if ( out.bad() ){ LOG4CXX_ERROR(_logger,"无法保存到 \""<<filename<<"\""); out.close(); return false; }
out.close(); return true; }
我还应该指出,您不需要使用save_str.c_str(),因为C++ iostreams(包括fstream、ofstream等)都能输出std::string对象。此外,如果在函数范围内构造文件流对象,它将在超出范围时自动关闭。

1
谁说save_str是一个std::string? :p 也许它是一个基于自定义char_traitsstd::basic_string<>模板,比如不区分大小写的traits之类的? :p - wilhelmtell
1
在这种情况下,它是 std::basic_string<unsigned char>。 - Truncheon

2
你是否确定save_file_handle没有与之关联(打开)的文件?如果有,那么调用它的open()方法将会失败,并引发其ios::failbit错误标志--以及任何设置的异常。 close()方法除非文件未打开,否则不会失败,在这种情况下,该方法将引发ios::failbit错误标志。无论如何,析构函数应该关闭文件,并在save_file_handle是堆栈变量时自动执行此操作,就像你代码中的情况一样。
int Saver::output()
{
    save_file_handle.open(file_name.c_str());
    if (save_file_handle.fail())
    {
        x_message("Error - file failed to previously close");
        return 0;
    }
    save_file_handle << save_str.c_str();

    if (save_file_handle.bad())
    {
        x_message("Error - failed to save file");
        return 0;
    }    
    return 1;
}

或者,如果您使用ios::exceptions(),则可以将错误检查与文件保存逻辑分开。

int Saver::output()
{
    ios_base::iostate old = save_file_handle.exceptions();
    save_file_handle.exceptions(ios::failbit | ios::badbit);
    try
    {
        save_file_handle.open(file_name.c_str());          
        save_file_handle << save_str.c_str();
    }
    catch (ofstream::failure e)
    {
        x_message("Error - couldn't save file");
        save_file_handle.exceptions(old);
        return 0;
    }
    save_file_handle.exceptions(old);
    return 1;
}

您可能更喜欢将save_file_handle.exceptions(ios::failbit | ios::badbit)的调用移动到构造函数中。然后,您可以摆脱重置异常标志的语句。


我建议不要在流I/O中使用异常,因为很少有人会期望流操作会抛出异常,并且当使用异常时恢复更加困难。 - anon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接