getline设置failbit及eof

5

我已经了解了这种行为的起源,因为在SO上有很多篇文章对此进行了详细的解释,其中一些著名的例子包括:

为什么在循环条件中使用iostream::eof被认为是错误的?

在不设置failbit的情况下使用getline()

当std::getline遇到eof时抛出异常

C++ istream EOF无法保证failbit?

而且它也包含在{{link5:std::getline标准}}中:

3) 如果由于任何原因(甚至是丢弃的分隔符)都没有提取出字符,则getline将设置failbit并返回。

我的问题是如何处理这种行为,在这种情况下,您希望流捕获failbit异常,除了由空行导致的eof之外的所有情况。我是否遗漏了一些明显的东西?

一个MWE:

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


void f(const std::string & file_name, char comment) {

std::ifstream file(file_name);
file.exceptions(file.failbit);
    try {
          std::string line;

          while (std::getline(file, line).good()) {
          // empty getline sets failbit throwing an exception
            if ((line[0] != comment) && (line.size() != 0)) {
                std::stringstream ss(line);
                // do stuff
            }
        }
    }

    catch (const std::ios_base::failure& e) {
        std::cerr << "Caught an ios_base::failure.\n"
        << "Explanatory string: " << e.what() << '\n'
        << "Error code: " << e.code() << '\n';

        }
}


int main() {

    f("example.txt", '#');
}

其中example.txt是一个以制表符分隔的文件,其最后一行仅包含\n字符:

# This is a text file meant for testing
0   9
1   8
2   7

编辑:

while(std::getline(file, line).good()){...} 复制了这个问题。

2个回答

3

避免设置failbit的另一种方法是,将if测试重构为检测读取空行。由于这是您在此情况下的最终行,因此您可以简单地使用return来避免抛出错误,例如:

    std::ifstream file (file_name);
    file.exceptions (file.failbit);
    try {
        std::string line;

        while (std::getline(file, line)) {
            // detect empty line and return
            if (line.size() == 0)
                return;
            if (line[0] != comment) {
                std::stringstream ss(line);
                // do stuff
            }
        }
    }
    ...

您的另一个选择是在catch中检查是否设置了eofbit。如果eofbit被设置--读取成功完成。例如:

    catch (const std::ios_base::failure& e) {
        if (!file.eof())
            std::cerr << "Caught an ios_base::failure.\n"
            << "Explanatory string: " << e.what() << '\n'
            << "Error code: " /* << e.code() */ << '\n';
    }

根据我的理解/测试,在std::getline(file,line)之后的line.size()==0语句将不起作用,因为在getline读取空行的情况下,它将设置failbit,退出while循环并导致异常。我使用if (line.size() != 0)的目的仅仅是为了避免在文件数据之间存在空行的情况下存储空行,而不是为了减轻异常抛出。 - gnikit
getline读取只包含'\n'的行时,会提取'\n',但不会存储在line中,gcount = 1,没有设置eofbitfailbit。当检测到line.size() = 0时,您知道唯一读取的是'\n'。它有效,我使用了您的数据文件进行测试:)在验证line.size() != 0之前,还避免调用line[0] != comment - David C. Rankin
2
@nikjohn - 另一个简单的修复方法是在catch的第一行包含if (!file.eof()) - David C. Rankin
那是我的原始方法,但该语句在这个最小化工作示例中绝对没有任何效果,这至少可以说是很奇怪的。 - gnikit
1
我怀疑这是由于在line为空时访问line[0] != comment所引起的@Arcinide注意到的UB的副作用。将您的测试反转为line.size() != 0 && line[0] != comment也可以避免这种情况。 - David C. Rankin

2

编辑: 我误解了原问题,请参考上面David的回答。这个答案是为了检查文件是否有终止换行符。

在你的while (getline)循环结束时,检查file.eof()

假设你刚刚对文件最后一行使用了std::getline()

  • 如果它后面有一个\n,那么std::getline()已经读取了分隔符,没有设置eofbit。(在这种情况下,接下来的std::getline()将设置eofbit。)

  • 而如果它后面没有\n,那么std::getline()已经读取了EOF并设置了eofbit

在这两种情况下,接下来的std::getline()都会触发failbit并进入您的异常处理程序。

PS:如果line为空,则if ((line[0] != comment) && (line.size() != 0)) {这一行是未定义行为。需要颠倒条件顺序。


1
我不确定检查 file.eof() 是否正确。无论情况如何(除非您添加 peek()),getline() 都将首先设置 eofbit,而不管循环内是否检查了 file.eof()。我不确定您打算如何实现检查?目前的错误报告为 basic_ios::clear: iostream erro,代码为 iostream:1 - David C. Rankin
如果文件以换行符结尾,getline 将设置 eofbit,然后下一个 getline 将设置 failbit。如果没有,则单个 getline 将同时发送 eofbitfailbit。检查将捕获此情况。我假设 OP 只想区分这两种情况。 - jcai
是的,我完全同意你对发生情况的分析,我唯一质疑的是循环内的 f.eof() 如何有所帮助。 - David C. Rankin
@DavidC.Rankin 啊,我明白你的意思了,似乎我误解了OP。你的解决方案是正确的。 - jcai

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