basic_ios中标记的语义

28

我发现自己一次又一次地被rdstate()标志所困惑 - good(), bad(), eof(), fail() - 以及它们在basic_ios::operator!, operator booloperator void*中的表达方式。

有人能够让我解脱,并解释一下这个问题,这样我就再也不用思考了吗?

2个回答

24

有三个标志表示错误状态:

  • badbit 表示流发生了严重错误。可能是缓冲区错误或者是提供数据给流的内容出现了错误。如果设置了此标志,那么你很可能不再使用该流。

  • failbit 表示从流中提取或读取失败(对于输出流来说,写入或插入失败),你需要意识到这种失败。

  • eofbit 表示输入流已经到达其末尾,没有剩余可读取的内容。请注意,只有在尝试从已经到达末尾的输入流中读取时才会设置此标志(也就是说,当尝试读取不存在的数据时会出现错误)。

failbit 也可能由许多到达 EOF 的操作设置。例如,如果流中仅剩空格,并尝试读取一个 int,那么你将同时到达 EOF 并且无法读取 int,因此两个标志都将被设置。

fail() 函数测试 badbit || failbit

good() 函数测试 !(badbit || failbit || eofbit)。也就是说,当没有任何标志被设置时,流是良好的。

你可以使用 ios::clear() 成员函数重置标志;这允许你设置任何错误标志;默认情况下(没有参数),它会清除所有三个标志。

流不会过载operator bool(); operator void*()用于实现安全bool习惯用语的一个有点瑕疵的版本。如果设置了badbitfailbit,则此运算符重载返回null,否则返回非null值。您可以使用此方法支持测试提取成功作为循环或其他控制流语句的条件的习惯用法:

if (std::cin >> x) {
    // extraction succeeded
}
else {
    // extraction failed
}
operator!()重载是operator void*()的相反操作;如果设置了badbitfailbit,则返回true,否则返回false。现在不再需要operator!()重载;它可以追溯到运算符重载完全和一致支持之前(请参见sbi的问题“为什么std::basic_ios重载一元逻辑非运算符?”)。
C++0x修复了导致我们不得不使用安全布尔习惯用法的问题,因此在C++0x中,basic_ios基类模板确实重载了operator bool()作为显式转换运算符;该运算符具有与当前的operator void*()相同的语义。

1
好的命名是错误的(因此被错误地应用),因为测试good()不能告诉您上一个操作是否成功。换句话说,stream.good()不等同于bool(stream)。 - Fred Nurk

17
除了James的回答之外,需要记住这些标志表示操作的结果,因此只有执行操作后才会设置它们。
常见错误是这样做:
#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream file("main.cpp");

    while (!file.eof()) // while the file isn't at eof...
    {
        std::string line;
        std::getline(file, line); // ...read a line...

        std::cout << "> " << line << std::endl; // and print it
    }
}

这里的问题在于只有在尝试获取最后一行后,eof()才会被设置。此时流将会说“不行,没有更多内容了!”并将其设置。这意味着“正确”的方式是:
#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream file("main.cpp");

    for (;;)
    {
        std::string line;
        std::getline(file, line); // read a line...

        if (file.eof()) // ...and check if it we were at eof
            break;

        std::cout << "> " << line << std::endl;
    }
}

这将检查放置在正确的位置。虽然这很混乱,但对我们来说很幸运的是,std::getline 的返回值是流,并且该流具有转换运算符,允许它在布尔上下文中进行测试,其值为 fail(),其中包括 eof()。因此,我们只需要写:

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream file("main.cpp");

    std::string line;
    while (std::getline(file, line)) // get line, test if it was eof
        std::cout << "> " << line << std::endl;
}

4
+1是为了强调这一点。在C++中,输入/输出操作经常被错误地执行。 - James McNellis
非常感谢,这个问题昨天在一个问答环节中也出现了,并且(惊讶!)一开始让我感到困惑,但是一旦你仔细考虑就会变得有意义了。 - Steve Townsend

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