管道到 /dev/null 时出现未指定的iostream_category错误(iOS::badbit 问题?



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

不幸的是,当我将程序的输出通过管道传输到/dev/null 时,我得到一个异常:ios_base::clear: unspecified iostream_category errorstderrorInappropriate ioctl for device.

这是我代码的相关部分:

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));
}

下面是一个异常示例:

~> ./Application test1.conf > /dev/null
test1.conf: # this is a line in the config file
Caught exception ios_base::clear: unspecified iostream_category error

当我不通过管道传输到/dev/null(而是连接到 stdout 或常规文件)时,一切都很好。我首先怀疑cout和cerr在哪里引起问题,但我不确定。

我终于发现我可以通过在打开文件后启用此行来解决此问题,以便忽略 badbit 类型的异常。

file.exceptions(std::ios::failbit);

坦率地说,我对C++太新手了,无法理解这里发生了什么。

我的问题:是什么导致了unspecified iostream_category异常?我怎样才能避免它?设置file.exceptions(std::ios::failbit);确实是一个正确的解决方案,还是会带来其他陷阱?(非常感谢指向一个很好的来源,详细说明在C++中打开文件的最佳实践,其中确实包括所有正确的异常处理,或一些背景解释!

我推荐以下方法。这是基于我自己的经验以及上面提供的一些链接。简而言之,我会提出以下建议:

  1. 请勿在使用C++流时启用例外。它们很难正确,我发现它们使我的代码可读性降低,这有点违背了异常的目的。(恕我直言,如果C++流默认以更合理的方式使用异常会更好。但是由于它不是以这种方式构建的,因此最好不要强迫问题,而只是遵循设计师似乎已经想到的模式。
  2. 依靠 getline 将正确处理各种流位的事实。您无需在每次调用后检查是否设置了错误或失败位。当这些发生时,getline 返回的流将被隐式转换为 false。
  3. 重新构建代码以遵循 RAII 模式,这样就不需要手动调用 open() 或 close()。这不仅简化了您的代码,而且确保您不会忘记关闭它。如果您不熟悉 RAII 模式,请参阅 https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization。
  4. 不要重复将 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).
}

我通过编写几个函数来改进我之前的答案,这些函数使用 lambda 处理流检查以实际处理文件。这具有以下优点:

  1. 您不要忘记将流检查放在哪里。
  2. 您的代码专注于您想要执行的操作(文件处理),而不是系统样板项目。

我创建了两个版本。对于第一个,您的lambda将获得流,您可以随心所欲地处理它。第二个时,您的 lambda 一次给出一行。在这两种情况下,如果发生 I/O 问题,它将引发system_error异常。您也可以在 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");
}

要使用它们,您可以按如下方式调用它们...

process_file("myfile.txt", [](ifstream& stream) {
... my processing on stream ...
});

process_file_line_by_line("myfile.txt", [](const string& line) {
... process the line ...
});

相关内容

最新更新