Как получить сообщение об ошибке при сбое открытия ifstream

99
ifstream f;
f.open(fileName);

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

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

Алекс Ф
источник
3
возможный дубликат C ++ ifstream Error Checking
Matthieu Rouget
3
@ Алекс Фарбер: Конечно. cerr << "Error code: " << strerror(errno); // Get some info as to whyкажется относящимся к вопросу.
Matthieu Rouget
@MatthieuRouget: проверьте возможный дубликат, который я опубликовал - похоже, это нестандартное поведение, реализованное только gcc.
arne
1
@MatthieuRouget: strerror(errno)работает. Отправьте это как ответ, я приму это.
Alex F

Ответы:

72

Каждый неудачный системный вызов обновляет errnoзначение.

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

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

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

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

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


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

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

Матье Руже
источник
1
e.what()похоже, не дает много информации, см. обновления моего ответа.
Arne Mertz
17
errnoиспользует локальное хранилище потоков в современных операционных системах. Однако нет никакой гарантии, что fstreamфункции не будут работать errnoпосле ошибки errno. Базовые функции могут вообще не устанавливаться errno(прямые системные вызовы в Linux или Win32). Это не работает во многих реализациях в реальном мире.
strcat
1
В MSVC e.what()всегда выводит одно и то же сообщение " iostream stream error"
rustyx
warning C4996: 'strerror': This function or variable may be unsafe. Consider using strerror_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. 1> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\string.h(168) : see declaration of 'strerror'
sergiol
1
@sergiol Это ложь. Игнорируйте их или отключите предупреждение.
SS Anne
29

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

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" (спасибо arne )

Если у 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";
}
Арне Мертц
источник
Спасибо. Я не тестировал это, потому что strerror(errno)опубликованный в комментариях работает и очень прост в использовании. Я думаю, что e.whatсработает, т.к. errnoработает.
Alex F
Затем посмотрите аннотации о многопоточности в ответе Маттья - я предполагаю, что e.what()это будет то strerror, что вернется в потокобезопасном режиме. Оба, вероятно, будут зависеть от платформы.
Arne Mertz
1
@AlexFarber: Я думаю, что ответ Арне лучше моего. Мое решение - это не способ решения вашей проблемы на языке C ++ . Однако я не нашел официальной информации о том, как библиотека C ++ сопоставляет ошибки системных вызовов exception.what(). Может быть, это хорошая возможность погрузиться в исходный код libstdc ++ :-)
Matthieu Rouget
Я попробовал это: попытался открыть несуществующий файл, и было прочитано сообщение об исключении basic_ios::clear, больше ничего. Это не очень полезно. Вот почему я не публиковал;)
arne
@arne какая платформа, компилятор, ОС?
Arne Mertz
22

Следуя ответу @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не существует.

rthur
источник
9
Для меня в MSVC 2015 это просто печатает iostream stream error.
rustyx
2
У меня GCC 6.3 тоже печатает iostream error. На каком компиляторе вы это тестировали? Действительно ли какой-либо компилятор предоставляет читаемую пользователем причину сбоя?
Руслан
2
Clang 6 на LIBC ++ на MacOS: unspecified iostream_category error.
аким
Xcode 10.2.1 (Clang) / libc ++ (C ++ 17) в MacOS 10.14.x: также «Неуказанная ошибка iostream_category». strerror (errno) КАЖЕТСЯ, это единственный способ сделать это правильно. Полагаю, я мог бы сначала поймать это, спросив std :: filesystem, если path.exists (), и проверив std :: error_code, который он возвращает.
SMGreenfield
8

Вы также можете бросить, 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)
euroburɳ
источник