Cómo obtener un mensaje de error cuando falla ifstream open

99
ifstream f;
f.open(fileName);

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

¿Cómo obtener un mensaje de error como cadena?

Alex F
fuente
3
posible duplicado de C ++ ifstream Error Checking
Matthieu Rouget
3
@Alex Farber: Claro. cerr << "Error code: " << strerror(errno); // Get some info as to whyparece relevante para la pregunta.
Matthieu Rouget
@MatthieuRouget: Verifique el posible duplicado que publiqué; parece que este es un comportamiento no estándar que solo implementa gcc.
arne
1
@MatthieuRouget: strerror(errno)funciona. Publique esto como respuesta, lo aceptaré.
Alex F

Respuestas:

72

Cada llamada al sistema que falla actualiza el errnovalor.

Por lo tanto, puede tener más información sobre lo que sucede cuando ifstreamfalla una apertura usando algo como:

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

Sin embargo, dado que cada llamada al sistema actualiza el errnovalor global , es posible que tenga problemas en una aplicación multiproceso si otra llamada al sistema desencadena un error entre la ejecución f.openy el uso de errno.

En sistema con estándar POSIX:

errno es un subproceso local; ponerlo en un hilo no afecta su valor en ningún otro hilo.


Editar (gracias a Arne Mertz y otras personas en los comentarios):

e.what() Al principio parecía ser una forma más C ++ - idiomáticamente correcta de implementar esto, sin embargo, la cadena devuelta por esta función depende de la implementación y (al menos en libstdc ++ de G ++) esta cadena no tiene información útil sobre la razón detrás del error ...

Matthieu Rouget
fuente
1
e.what()no parece dar mucha información, vea actualizaciones a mi respuesta.
Arne Mertz
17
errnoutiliza almacenamiento local de subprocesos en sistemas operativos modernos. Sin embargo, no hay garantía de que las fstreamfunciones no funcionen errnodespués de que se produzca un error. Es posible que las funciones subyacentes no se establezcan errnoen absoluto (llamadas directas al sistema en Linux o Win32). Esto no funciona en muchas implementaciones del mundo real.
strcat
1
En MSVC, e.what()siempre imprime el mismo mensaje " 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 Esas son mentiras. Ignórelos o desactive la advertencia.
SS Anne
29

Podría intentar dejar que la transmisión arroje una excepción en caso de falla:

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(), sin embargo, no parece ser muy útil:

  • Lo probé en Win7, Embarcadero RAD Studio 2010 donde da "ios_base :: failbit set" mientras que strerror(errno)da "No existe tal archivo o directorio".
  • En Ubuntu 13.04, gcc 4.7.3 la excepción dice "basic_ios :: clear" (gracias a arne )

Si e.what()no funciona para usted (no sé qué le dirá sobre el error, ya que no está estandarizado), intente usar std::make_error_condition(solo 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
fuente
Gracias. No probé esto porque strerror(errno)publicado en los comentarios funciona y es muy simple de usar. Creo que e.whatfuncionará, ya que errnofunciona.
Alex F
Luego, vea las anotaciones sobre subprocesos múltiples en la respuesta de Matthieus: supongo que e.what()eso será lo que strerrorregrese, de una manera segura para subprocesos. Ambos probablemente dependerán de la plataforma.
Arne Mertz
1
@AlexFarber: Creo que la respuesta de Arne es mejor que la mía. Mi solución no es la forma de C ++ de resolver su problema. Sin embargo, no encontré información oficial sobre cómo la biblioteca C ++ mapea los errores de llamada del sistema a exception.what(). Puede ser una buena oportunidad para sumergirse en el código fuente de libstdc ++ :-)
Matthieu Rouget
Probé esto: intenté abrir un archivo inexistente y se leyó el mensaje de excepción basic_ios::clear, nada más. Esto no es realmente útil. Por eso no
publiqué
@arne ¿qué plataforma, compilador, sistema operativo?
Arne Mertz
22

Siguiendo la respuesta de @Arne Mertz, a partir de C ++ 11 std::ios_base::failurehereda de system_error(consulte http://www.cplusplus.com/reference/ios/ios_base/failure/ ), que contiene tanto el código de error como el mensaje que strerror(errno)devolvería.

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

Esto se imprime No such file or directory.si fileNameno existe.

rthur
fuente
8
Para mí en MSVC 2015 eso solo se imprime iostream stream error.
rustyx
2
Para mí, GCC 6.3 también se imprime iostream error. ¿En qué compilador probaste esto? ¿Algún compilador proporciona realmente una razón legible por el usuario para el error?
Ruslan
2
Tañido 6 en la libc ++ en MacOS: unspecified iostream_category error.
akim
Xcode 10.2.1 (Clang) / libc ++ (C ++ 17) en MacOS 10.14.x: también "Error de iostream_category no especificado". strerror (errno) PARECE ser la única forma de hacerlo bien. Supongo que podría detectarlo primero preguntando a std :: filesystem si el path.exists () y examinando el std :: error_code que devuelve.
SMGreenfield
7

También puede lanzar un std::system_errorcomo se muestra en el código de prueba a continuación. Este método parece producir una salida más legible que 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;
}

Salida de ejemplo (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ɳ
fuente