C++ 管道传输到/dev/null时出现未指定的iostream_类别错误(ios::badbit问题?)

C++ 管道传输到/dev/null时出现未指定的iostream_类别错误(ios::badbit问题?),c++,exception,io,std,C++,Exception,Io,Std,我有解析配置文件的代码,如果出现错误,它可能会将输出发送到stdout或stderr 不幸的是,当我将程序的输出管道连接到/dev/null时,我得到了一个异常:ios_base::clear:unspecified iostream_category error,其中stderror设备的ioctl不合适 以下是我的代码的相关部分: try { file.exceptions(std::ios::failbit | std::ios::badbit); file.open(co

我有解析配置文件的代码,如果出现错误,它可能会将输出发送到stdout或stderr

不幸的是,当我将程序的输出管道连接到/dev/null时,我得到了一个异常:
ios_base::clear:unspecified iostream_category error
,其中stderror
设备的ioctl不合适

以下是我的代码的相关部分:

try {
    file.exceptions(std::ios::failbit | std::ios::badbit);
    file.open(config_file);
    // file.exceptions(std::ios::failbit);
}
catch (std::ios_base::failure& e) {
    throw std::invalid_argument(std::string("Can't open config file ") + config_file + ": " + strerror(errno));
}
try {
    errno = 0; // reset errno before I/O operation.
    // ...
    while (std::getline(file, line)) {
        if ( [... unknown line ...] ) {
            std::cerr << "Invalid line in config " << config_file << ": " << line << std::endl;
            continue;
        }

        // debug info to STDOUT:
        std::cout << config_file << ": " << line << std::endl;

    }
} catch (std::ios_base::failure& err) {
    std::cout << "Caught exception " << err.what() << std::endl;
    if (errno != 0) {
        char* theerror = strerror(errno);
        file.close();
        throw std::invalid_argument(std::string("Can't read config file ") + config_file + ": " + theerror);
    }
}
try {
    file.close();
}
catch (std::ios_base::failure& e) {
    throw std::invalid_argument(std::string("Can't close config file ") + config_file + ": " + strerror(errno));
}
当我不使用管道连接到
/dev/null
(而是连接到标准输出或常规文件)时,一切都很好。我首先怀疑cout和cerr在哪里引起了问题,但我不确定

我最终发现,我可以通过在打开文件后启用这一行来解决这个问题,从而忽略badbit类型的异常

file.exceptions(std::ios::failbit);
坦白地说,我在C++方面太新手了,无法理解这里发生了什么。
我的问题:是什么导致了未指定的iostream\u类别异常?我怎样才能避免呢?正在设置
file.exceptions(std::ios::failbit)确实是一个正确的解决方案,还是会带来其他陷阱?(一个指向一个好的源代码的详细说明,详细说明了在C++中打开文件的最佳实践,其中包括所有适当的异常处理,或者一些背景说明,非常感谢!)

< P>我推荐下面的方法。这是基于我自己的经验以及上面提供的一些链接。总之,我建议如下:

using namespace std;
try {
    errno = 0;

    // Construct the stream here (part of RAII) then you don't need to call
    // open() or close() manually.
    ifstream file(config_file);   
    if (!file.is_open()) {
        throw invalid_argument("Could not open the file");
    }

    while (getline(file, line)) {
        // do all your processing as if no errors will occur (getline will 
        // be explicitly cast to a false when an error occurs). If there is
        // something wrong with a line (bad format, etc.) then throw an
        // exception without echoing it to cerr.
    }
    if (file.bad()) {
        throw invalid_argument("Problem while reading file");
    }
}
catch (const invalid_argument& e) {
    // Whatever your error handling needs to be. config_file should still
    // be valid so you can add it here, you don't need to add it to each
    // individual exception. Also you can echo to cerr here, you don't need
    // to do it inside the loop. If you want to use errno it should still
    // be set properly at this point. If you want to pass the exception to
    // the next level you can either rethrow it or generate a new one that
    // adds additional details (like strerror and the filename).
}
使用C++流时不要打开异常。它们是如此难以正确,我发现它们使我的代码可读性降低,这有点违背了异常的目的。(IHO,如果C++流默认情况下使用异常,并且更合理的方式会更好。但是,由于它不是以这样的方式构建的,所以最好不要强制这个问题,而是按照设计者们似乎已经想到的模式。)
  • 依靠getline将正确处理各种流位这一事实。您不需要在每次调用后检查是否设置了坏位或失败位。当发生这些情况时,getline返回的流将隐式转换为false
  • 按照RAII模式重新构造代码,这样就不需要手动调用open()或close()。这不仅简化了代码,而且确保您不会忘记关闭它。如果您不熟悉RAII模式,请参阅
  • 不要反复将errno或文件名之类的内容放入生成的异常中。尽量减少错误以避免重复,并在底部使用catch块来处理错误,可能会抛出一个新的异常,添加您希望报告的详细信息
  • 话虽如此,我建议您重新编写代码,使其看起来如下所示:

    using namespace std;
    try {
        errno = 0;
    
        // Construct the stream here (part of RAII) then you don't need to call
        // open() or close() manually.
        ifstream file(config_file);   
        if (!file.is_open()) {
            throw invalid_argument("Could not open the file");
        }
    
        while (getline(file, line)) {
            // do all your processing as if no errors will occur (getline will 
            // be explicitly cast to a false when an error occurs). If there is
            // something wrong with a line (bad format, etc.) then throw an
            // exception without echoing it to cerr.
        }
        if (file.bad()) {
            throw invalid_argument("Problem while reading file");
        }
    }
    catch (const invalid_argument& e) {
        // Whatever your error handling needs to be. config_file should still
        // be valid so you can add it here, you don't need to add it to each
        // individual exception. Also you can echo to cerr here, you don't need
        // to do it inside the loop. If you want to use errno it should still
        // be set properly at this point. If you want to pass the exception to
        // the next level you can either rethrow it or generate a new one that
        // adds additional details (like strerror and the filename).
    }
    

    我在前面的答案的基础上做了改进,编写了两个函数,使用lambdas处理流检查,以实际处理文件。这有以下优点:

  • 您不会忘记将流检查放在哪里
  • 您的代码集中于您想要做的事情(文件处理),而不是系统样板项目
  • 我已经创建了两个版本。第一次给你的lambda流,你可以随心所欲地处理它。第二次,你的lambda一次只给一行。在这两种情况下,如果发生I/O问题,它将抛出系统错误异常。您还可以在lambda中抛出自己的异常,它们将被正确地传递

    namespace {
        inline void throwProcessingError(const string& filename, const string& what_arg) {
            throw system_error(errno, system_category(), what_arg + " " + filename);
        }
    }
    
    void process_file(const string& filename, function<void (ifstream &)> fn) {
        errno = 0;
        ifstream strm(filename);
        if (!strm.is_open()) throwProcessingError(filename, "Failed to open");
        fn(strm);
        if (strm.bad()) throwProcessingError(filename, "Failed while processing");
    }
    
    void process_file_line_by_line(const string& filename, function<void (const string &)> fn)
    {
        errno = 0;
        ifstream strm(filename);
        if (!strm.is_open()) throwProcessingError(filename, "Failed to open");
        string line;
        while (getline(strm, line)) {
            fn(line);
        }
        if (strm.bad()) throwProcessingError(filename, "Failed while processing");
    }
    


    我仍然希望得到一个好的答案,但我发现的最接近的是一个相关的问题,以及一篇由Jan Philip Gehrcke撰写的文章的链接。
    process_file("myfile.txt", [](ifstream& stream) {
        ... my processing on stream ...
    });
    
    process_file_line_by_line("myfile.txt", [](const string& line) {
        ... process the line ...
    });