Как получить сообщение об ошибке, когда ifstream open терпит неудачу


ifstream f;
f.open(fileName);

if ( f.fail() )
{
    // I need error message here, like "File not found" etc. -
    // the reason of the failure
}

Как получить сообщение об ошибке в виде строки?

4 68

4 ответа:

каждый системный вызов, который не удается обновить errno значение.

таким образом, вы можете иметь больше информации о том, что происходит, когда ifstream открыть не удается, используя что-то вроде :

cerr << "Error: " << strerror(errno);

С системный вызов обновляет глобальный errno значение, вы можете иметь проблемы в многопоточном приложении, если другой системный вызов вызывает ошибку между исполнением f.open и использовать errno.

в системе со стандартом POSIX:

errno является локальным потоком; установка его в одном потоке не влияет на его значение в любом другом потоке.


Edit (спасибо Арне Мерцу и другим людям в комментариях):

e.what() сначала казалось, что это более C++-идиоматически правильный способ реализации этого, однако строка, возвращаемая этой функцией, зависит от реализации и (at по крайней мере, в G++'S libstdc++) эта строка не содержит полезной информации о причине ошибки...

вы можете попробовать разрешить потоку выдавать исключение при сбое:

std::ifstream f;
//prepare f to throw if failbit gets set
std::ios_base::iostate exceptionMask = f.exceptions() | std::ios::failbit;
f.exceptions(exceptionMask);

try {
  f.open(fileName);
}
catch (std::ios_base::failure& e) {
  std::cerr << e.what() << '\n';
}

e.what(), однако, не кажется очень полезным:

  • я попробовал его на Win7, Embarcadero RAD Studio 2010, где он дает "ios_base::failbit set", тогда как strerror(errno) выдает "Нет такого файла или каталога."
  • на Ubuntu 13.04, gcc 4.7.3 исключение говорит " basic_ios:: clear "(спасибо Арне)

если e.what() не работает вы (я не знаю, что он скажет вам об ошибке, так как это не стандартизировано), попробуйте использовать std::make_error_condition (только C++11):

catch (std::ios_base::failure& e) {
  if ( e.code() == std::make_error_condition(std::io_errc::stream) )
    std::cerr << "Stream error!\n"; 
  else
    std::cerr << "Unknown failure opening file.\n";
}

после ответа @Arne Mertz, начиная с C++11 std::ios_base::failure наследует от system_error (см. http://www.cplusplus.com/reference/ios/ios_base/failure/), который содержит как код ошибки, так и сообщение о том, что strerror(errno) вернется.

std::ifstream f;

// Set exceptions to be thrown on failure
f.exceptions(std::ifstream::failbit | std::ifstream::badbit);

try {
    f.open(fileName);
} catch (std::system_error& e) {
    std::cerr << e.code().message() << std::endl;
}

это выводит No such file or directory. если fileName не существует.

вы также можете бросить std::system_error как показано в тестовом коде ниже. Этот метод, кажется, производит более читаемый вывод, чем f.exception(...).

#include <exception> // <-- requires this
#include <fstream>
#include <iostream>

void process(const std::string& fileName) {
    std::ifstream f;
    f.open(fileName);

    // after open, check f and throw std::system_error with the errno
    if (!f)
        throw std::system_error(errno, std::system_category(), "failed to open "+fileName);

    std::clog << "opened " << fileName << std::endl;
}

int main(int argc, char* argv[]) {
    try {
        process(argv[1]);
    } catch (const std::system_error& e) {
        std::clog << e.what() << " (" << e.code() << ")" << std::endl;
    }
    return 0;
}

пример вывода (Ubuntu w / clang):

$ ./test /root/.profile
failed to open /root/.profile: Permission denied (system:13)
$ ./test missing.txt
failed to open missing.txt: No such file or directory (system:2)
$ ./test ./test
opened ./test
$ ./test $(printf '%0999x')
failed to open 000...000: File name too long (system:36)