Jak uzyskać komunikat o błędzie, gdy otwarcie ifstream nie powiedzie się


99
ifstream f;
f.open(fileName);

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

Jak uzyskać komunikat o błędzie w postaci ciągu?




3
@Alex Farber: Jasne. cerr << "Error code: " << strerror(errno); // Get some info as to whywydaje się mieć znaczenie dla pytania.
Matthieu Rouget

@MatthieuRouget: Sprawdź możliwy duplikat, który opublikowałem - wygląda na to, że jest to niestandardowe zachowanie zaimplementowane tylko przez gcc.
arne

1
@MatthieuRouget: strerror(errno)działa. Opublikuj to jako odpowiedź, zaakceptuję to.
Alex F

Odpowiedzi:


72

Każde niepowodzenie wywołania systemowego aktualizuje errnowartość.

W ten sposób możesz uzyskać więcej informacji o tym, co się dzieje, gdy ifstreamotwarcie się nie powiedzie, używając czegoś takiego:

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

Ponieważ jednak każde wywołanie systemowe aktualizuje errnowartość globalną , mogą wystąpić problemy w aplikacji wielowątkowej, jeśli inne wywołanie systemowe wyzwoli błąd między wykonaniem f.opena użyciem errno.

W systemie ze standardem POSIX:

errno jest lokalnym wątkiem; ustawienie go w jednym wątku nie wpływa na jego wartość w żadnym innym wątku.


Edytuj (podziękowania dla Arne Mertza i innych osób w komentarzach):

e.what() początkowo wydawało się, że jest to bardziej C ++ - idiomatycznie poprawny sposób implementacji tego, jednak ciąg zwracany przez tę funkcję jest zależny od implementacji i (przynajmniej w libstdc ++ w G ++) ten ciąg nie zawiera przydatnych informacji o przyczynie błędu ...


1
e.what()nie wydaje się udzielać wielu informacji, zobacz aktualizacje mojej odpowiedzi.
Arne Mertz

17
errnoużywa magazynu lokalnego wątku w nowoczesnych systemach operacyjnych. Nie ma jednak gwarancji, że fstreamfunkcje nie będą blokować się errnopo wystąpieniu błędu. Podstawowe funkcje mogą errnow ogóle nie być ustawione (bezpośrednie wywołania systemowe w systemie Linux lub Win32). To nie działa w wielu rzeczywistych implementacjach.
strcat

1
W MSVC, e.what()zawsze wypisuje ten sam komunikat " 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 To są kłamstwa. Zignoruj ​​je lub wyłącz ostrzeżenie.
SS Anne

29

Możesz spróbować pozwolić strumieniowi zgłosić wyjątek w przypadku niepowodzenia:

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()jednak nie wydaje się być zbyt pomocne:

  • Wypróbowałem to na Win7, Embarcadero RAD Studio 2010, gdzie daje "ios_base :: failbit set", a strerror(errno)daje "No such file or directory".
  • W Ubuntu 13.04, gcc 4.7.3 wyjątek mówi „basic_ios :: clear” (dzięki arne )

Jeśli e.what()nie działa dla Ciebie (nie wiem, co powie ci o błędzie, ponieważ nie jest to ustandaryzowane), spróbuj użyć std::make_error_condition(tylko 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";
}

Dzięki. Nie testowałem tego, ponieważ strerror(errno)zamieszczone w komentarzach działa i jest bardzo proste w użyciu. Myślę, że e.whatto zadziała, ponieważ errnodziała.
Alex F

Następnie spójrz na adnotacje o wielowątkowości w odpowiedzi Matthieusa - przypuszczam, że e.what()będzie to to strerror, co powróci w sposób bezpieczny dla wątków. Oba będą prawdopodobnie zależne od platformy.
Arne Mertz

1
@AlexFarber: Myślę, że odpowiedź Arne jest lepsza niż moja. Moje rozwiązanie nie jest sposobem C ++ na rozwiązanie twojego problemu. Jednak nie znalazłem oficjalnych informacji o tym, jak biblioteka C ++ odwzorowuje błędy wywołań systemowych na exception.what(). Może być dobra okazja, aby zagłębić się w kod źródłowy libstdc ++ :-)
Matthieu Rouget

Wypróbowałem to: próbowałem otworzyć nieistniejący plik i odczytano komunikat o wyjątku basic_ios::clear, nic więcej. To nie jest naprawdę pomocne. Dlatego nie wysłałem;)
arne

@arne która platforma, kompilator, system operacyjny?
Arne Mertz

22

Kontynuując odpowiedź @Arne Mertz, od C ++ 11 std::ios_base::failuredziedziczy z system_error(patrz http://www.cplusplus.com/reference/ios/ios_base/failure/ ), który zawiera zarówno kod błędu, jak i komunikat, strerror(errno)który powróci.

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

To jest drukowane, No such file or directory.jeśli fileNamenie istnieje.


9
Dla mnie w MSVC 2015, który po prostu drukuje iostream stream error.
rustyx

2
U mnie też drukuje GCC 6.3 iostream error. Na jakim kompilatorze to przetestowałeś? Czy jakikolwiek kompilator faktycznie podaje czytelną dla użytkownika przyczynę niepowodzenia?
Ruslan,

2
Dzyń 6 na libc ++ na MacOS: unspecified iostream_category error.
akim

Xcode 10.2.1 (Clang) / libc ++ (C ++ 17) w systemie MacOS 10.14.x: również „Nieokreślony błąd iostream_category”. WYDAJE SIĘ, że strerror (errno) jest jedynym sposobem, aby to naprawić. Przypuszczam, że mógłbym go najpierw złapać, pytając std :: filesystem, czy path.exists (), i sprawdzając std :: error_code, który zwraca.
SMGreenfield

7

Możesz również rzucić, std::system_errorjak pokazano w kodzie testowym poniżej. Wydaje się, że ta metoda daje bardziej czytelne wyniki niż 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;
}

Przykładowe dane wyjściowe (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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.