C ++ łapie wszystkie wyjątki

244

Czy istnieje odpowiednik języka Java w języku C ++?

try {
    ...
}
catch (Throwable t) {
    ...
}

Próbuję debugować kod Java / jni, który wywołuje rodzime funkcje systemu Windows, a maszyna wirtualna ulega awarii. Kod macierzysty pojawia się dobrze w testach jednostkowych i wydaje się zawieszać tylko po wywołaniu przez jni. Ogólny mechanizm wychwytywania wyjątków okazałby się niezwykle przydatny.

Obediah Stane
źródło
2
Zauważ, że większość awarii nie jest spowodowana wyjątkami w C ++. Możesz wychwycić wszystkie wyjątki, ale to nie zapobiegnie wielu awariom.
Mooing Duck

Odpowiedzi:

335
try{
    // ...
} catch (...) {
    // ...
}

wyłapie wszystkie wyjątki w C ++, ale należy to uznać za zły projekt. Możesz użyć nowego mechanizmu wyjątku current_exception c ++ 11, ale jeśli nie masz możliwości korzystania z c ++ 11 (starsze systemy kodowe wymagające przepisania), to nie masz wskazanego wskaźnika wyjątku, aby użyć wiadomości lub nazwy . Możesz dodać osobne klauzule catch dla różnych wyjątków, które możesz przechwycić, i łap tylko wszystko na dole, aby zarejestrować nieoczekiwany wyjątek. Na przykład:

try{
    // ...
} catch (const std::exception& ex) {
    // ...
} catch (const std::string& ex) {
    // ...
} catch (...) {
    // ...
}
Greg D.
źródło
68
Dobrą praktyką jest wychwytywanie wyjątków przez stałe odniesienie. Jak w: catch (std ::
ception
12
@coryan: Dlaczego dobrą praktyką jest łapanie według stałej referencji?
Tim MB
19
Unikanie niepotrzebnych kopii to jedna korzyść.
Greg D
21
-1: sugestia, że ​​„złapie wszystkie wyjątki w C ++”, jest myląca. Spróbuj wygenerować błąd dzielenia przez zero w bloku try. Zobaczysz, że wygeneruje wyjątek, który nie zostanie przechwycony, ale kod jest wyraźnie w C ++. Bardziej pomocne byłoby stwierdzenie, że to „złapie wszystkie wyjątki w C ++”, a następnie doda pewne wzmianki o wyjątkach strukturalnych do uwag o ograniczonej użyteczności.
omatai
42
@omatai: Naprawiono, przechwytuje wszystkie wyjątki C ++. Dzielenie przez zero jest niezdefiniowanym zachowaniem i nie generuje wyjątku C ++.
Mooing Duck
151

Ktoś powinien dodać, że nie można złapać „awarii” w kodzie C ++. Nie rzucają wyjątków, ale robią wszystko, co im się podoba. Gdy widzisz, że program ulega awarii z powodu odwołania się do wskaźnika zerowego, zachowuje się w sposób niezdefiniowany. Nie ma std::null_pointer_exception. Próba wyłapania wyjątków nie pomoże.

Na wszelki wypadek ktoś czyta ten wątek i myśli, że może uzyskać przyczynę awarii programu. Zamiast tego należy użyć debugera, takiego jak gdb.

Johannes Schaub - litb
źródło
4
Cóż, jak zauważa Shy, jest to możliwe dzięki kompilatorowi VC. To nie jest dobry pomysł, ale jest to możliwe.
Shog9,
7
tak z SEH. ale nie z rozsądnymi standardowymi technikami c ++ :) cóż, jeśli trzymasz się okien, możesz prawie wszystko zrobić :)
Johannes Schaub - litb
1
Mmm ... dzięki za ten smakołyk. Szukałem odpowiedzi na pytanie, dlaczego moje wyjątki zerowe nie zostały złapane!
Dalin Seivewright,
10
Możesz wykryć awarie za pomocą SEH w systemie Windows i sygnału (2) / sigaction (2) w systemach POSIX, co obejmuje ogromną większość używanych obecnie systemów, ale podobnie jak obsługa wyjątków, nie jest to coś, co powinno być używane do normalnej kontroli przepływu. To bardziej „zrób coś pożytecznego przed śmiercią”.
Adam Rosenfield
1
@AdamRosenfield, dopóki nie zaimplementowałeś try { .. } catch(...) { ... }przechwytywania za pomocą sygnału / sigaction, nie nazwałbym tego „przechwytywaniem” :) Jeśli w procedurze obsługi sygnału, programiście stosunkowo trudno jest wiedzieć, gdzie w kodzie doszło do awarii (mówię o programowym wykrywaniu tego), w porównaniu do try / catch.
Johannes Schaub - litb
72

Oto, w jaki sposób możesz dokonać inżynierii wstecznej typu wyjątku od wewnątrz, catch(...)jeśli zajdzie taka potrzeba (może być przydatny podczas przechwytywania nieznanego z biblioteki innej firmy) za pomocą GCC:

#include <iostream>

#include <exception>
#include <typeinfo>
#include <stdexcept>

int main()
{
    try {
        throw ...; // throw something
    }
    catch(...)
    {
        std::exception_ptr p = std::current_exception();
        std::clog <<(p ? p.__cxa_exception_type()->name() : "null") << std::endl;
    }
    return 1;
}

a jeśli możesz sobie pozwolić na korzystanie ze wzmocnienia , możesz uczynić sekcję catch jeszcze prostszą (na zewnątrz) i potencjalnie międzyplatformową

catch (...)
{
    std::clog << boost::current_exception_diagnostic_information() << std::endl;
}
Bobah
źródło
58
try {
   // ...
} catch (...) {
   // ...
}

Zauważ, że ...wnętrze catchjest prawdziwą elipsą, tj. trzy kropki.

Ponieważ jednak wyjątki w C ++ niekoniecznie są podklasami Exceptionklasy podstawowej , nie ma żadnego sposobu, aby zobaczyć zmienną wyjątku, która jest generowana podczas korzystania z tej konstrukcji.

Greg Hewgill
źródło
24
W C ++ 11 jest: try {std :: string (). At (1); // generuje to std :: out_of_range} catch (...) {eptr = std :: current_exception (); // capture}
Mohammad Alaggan
2
@bfontaine: Cóż, tak, ale powiedziałem, że aby odróżnić catchspecyfikator od istniejącego symbolu zastępczego kodu w komentarzu ( // ...), który oczywiście nie jest składnią C ++.
Greg Hewgill
1
@GregHewgill: tak, to było po prostu typograficzne naciąganie.
bfontaine
1
@bfontaine: Wystarczająco sprawiedliwe. :)
Greg Hewgill
44

nie jest możliwe (w C ++) wyłapanie wszystkich wyjątków w przenośny sposób. Jest tak, ponieważ niektóre wyjątki nie są wyjątkami w kontekście C ++. Obejmuje to między innymi dzielenie przez zero błędów i inne. Możliwe jest włamanie się i uzyskanie możliwości rzucania wyjątków, gdy wystąpią te błędy, ale nie jest to łatwe i na pewno nie jest łatwo dostać się w przenośny sposób.

Jeśli chcesz złapać wszystkie wyjątki STL, możesz to zrobić

try { ... } catch( const std::exception &e) { ... }

Który pozwoli ci użyć e.what(), który zwróci a const char*, która może powiedzieć ci więcej o samym wyjątku. Jest to konstrukcja, która najbardziej przypomina konstrukcję Java.

Nie pomoże ci to, jeśli ktoś jest na tyle głupi, aby rzucić wyjątek, który nie dziedziczy std::exception.

Jaśniejsze
źródło
2
dlaczego nie jest na górze?
Ivan Sanz-Carasa,
31

Krótko mówiąc, użyj catch(...). Należy jednak pamiętać, że catch(...)należy go używać w połączeniu z throw;:

try{
    foo = new Foo;
    bar = new Bar;
}
catch(...)       // will catch all possible errors thrown. 
{ 
    delete foo;
    delete bar;
    throw;       // throw the same error again to be handled somewhere else
}

To jest właściwy sposób użycia catch(...).

Mellester
źródło
6
lepiej jest użyć RAII do zarządzania pamięcią, która automatycznie obsługuje te wyjątkowe sytuacje.
paykoob
1
@paykoob Jak radzi sobie z przypadkami, w których dokonano modyfikacji w celu utworzenia nowego foo, ale nie udało się to na pasku. Lub gdy konstruktor bar próbuje otworzyć plik, ale kończy się niepowodzeniem i dlatego rzuca. wtedy możesz skończyć z oszałamiającym foo
Mellester
2
@MelleSterk Czy stos nie zostałby wyczyszczony w takim przypadku, co uruchomiłoby Foodestruktor? Myślałem, że o to właśnie chodzi w RAII. Jeśli jednak potrzebujesz wskaźnika do Fooraczej niż tylko tworzenia Foostosu, musisz owinąć wskaźnik w coś innego, co zadeklarowano na stosie.
reirab
tak auto foo = std :: make_unique <Foo> (); auto bar = std :: make_unique <Bar> (); // jest wyjątkowo bezpieczny i nie wycieknie, nie wymaga zaczepu (...)
paulm 13.04.15
Ta odpowiedź zasługuje na głos, choćby na rozpoczęcie dyskusji :)
Cristik
21

można to zrobić pisząc:

try
{
  //.......
}
catch(...) // <<- catch all
{
  //.......
}

Istnieje jednak bardzo niezauważalne ryzyko: nie można znaleźć dokładnego rodzaju błędu, który został zgłoszony w trybloku, więc skorzystaj z tego rodzaju, catchgdy masz pewność, że bez względu na rodzaj wyjątku program musi pozostać w sposób określony w catchbloku.

Infintyyy
źródło
31
Mam nadzieję, że dostałeś jakąś odznakę za udzielenie odpowiedzi na pytanie prawie 5 lat po udzieleniu doskonałej odpowiedzi!
4
@DrEval Jak sobie życzysz;) stackoverflow.com/help/badges/17/necromancer?userid=2580505
Andreas
18

Możesz użyć

catch(...)

ale to jest bardzo niebezpieczne. W swojej książce Debugowanie systemu Windows John Robbins opowiada historię wojenną o naprawdę paskudnym błędzie, który został zamaskowany poleceniem catch (...). Lepiej złapać określone wyjątki. Złap wszystko, co Twoim zdaniem blok try może rzucić, ale niech kod wyrzuci wyjątek wyżej, jeśli wydarzy się coś naprawdę nieoczekiwanego.

John D. Cook
źródło
1
Właśnie złapałem niektóre z nich i włączyłem trochę logowania na tym etapie. Nie robienie nic z wyjątkiem zdecydowanie prosi o kłopoty.
jxramos
15

Pozwól mi tylko wspomnieć o tym tutaj: Java

try 
{
...
}
catch (Exception e)
{
...
}

NIE może złapać wszystkich wyjątków! W rzeczywistości zdarzyło mi się wcześniej, że takie rzeczy się zdarzają i jest to bardzo prowokujące; Wyjątek pochodzi od Throwable. Więc dosłownie, aby złapać wszystko, NIE CHCESZ uchwycić wyjątków; chcesz złapać Throwable.

Wiem, że to brzmi podejrzanie, ale kiedy spędziłeś kilka dni próbując dowiedzieć się, skąd pochodzi „nieprzechwycony wyjątek” w kodzie otoczonym blokiem try ... catch (Exception e) ", pozostaje przy ty.

Paul Sonier
źródło
2
Oczywiście, nigdy nie powinieneś łapać obiektów Error - gdybyś miał je złapać, byłyby to wyjątki. Obiekty błędów są całkowicie śmiertelne, takie jak brak miejsca na stosie itp.
SCdF
1
Żadne wyjątki czasu wykonywania, które są najczęściej GoodProgrammerExpected wyjątki !!!
OscarRyz
3
Mieliśmy naprawdę poważny błąd spowodowany złapaniem OutOfMemoryError z powodu bloku catch (Throwable) zamiast pozwalania mu zabijać rzeczy ...
Trejkaz
1
Oczywiście catch(Exception)może nie wychwycić wszystkich wyjątków w Javie, mieszasz się z C # ... Java = catch(Thowable), C # = catch(Exception). Nie pomyl ich.
Szef kuchni Faraon
2
@OscarRyz To brzmi jak CoderMalfunctionError(która w rzeczywistości jest prawdziwą Errorpodklasą Java ... chociaż to nie znaczy, jak to brzmi.)
reirab
9

Cóż, jeśli chcesz złapać cały wyjątek, aby na przykład utworzyć minidump ...

Ktoś wykonał pracę w systemie Windows.

Zobacz http://www.codeproject.com/Articles/207464/Exception-Handling-in-Visual-Cplusplus W artykule wyjaśnia, w jaki sposób dowiedział się, jak wychwycić wszelkiego rodzaju wyjątki, i podaje kod, który działa.

Oto lista, którą możesz złapać:

 SEH exception
 terminate
 unexpected
 pure virtual method call
 invalid parameter
 new operator fault 
 SIGABR
 SIGFPE
 SIGILL
 SIGINT
 SIGSEGV
 SIGTERM
 Raised exception
C++ typed exception

I zastosowanie: CCrashHandler ch; ch.SetProcessExceptionHandlers (); // zrób to dla jednego wątku ch.SetThreadExceptionHandlers (); // za każdy thred


Domyślnie tworzy to minidump w bieżącym katalogu (crashdump.dmp)

Po szoku
źródło
4

Ogólny mechanizm wychwytywania wyjątków okazałby się niezwykle przydatny.

Wątpliwy. Wiesz już, że Twój kod jest uszkodzony, ponieważ ulega awarii. Wyjątki od jedzenia mogą to maskować, ale prawdopodobnie spowoduje to jeszcze bardziej paskudne, subtelniejsze błędy.

To, czego naprawdę chcesz, to debugger ...

Shog9
źródło
13
Nie zgadzam się, istnieje wiele przypadków w aplikacjach czasu rzeczywistego, w których wolałbym złapać nieznany wyjątek, napisać cokolwiek w dzienniku / zastosować ogólny ogólny błąd, zamiast pozwolić aplikacji na awarię.
f0ster
3
Ja raczej podejrzewam myślisz o przypadkach, gdzie mogą odbywać część ogólną kursu błędzie działania, wygodnie pomijając te, gdzie stos jest zaśmiecona lub pamięć jest wyczerpana i rodzajowy obsługi błędów nie będzie uwzględniony. Nic złego w wychwytywaniu błędów, które można odzyskać, ale IMHO catch-all powinno naprawdę istnieć tylko jako izolowany (osobny stos, wstępnie przydzielona pamięć), starannie napisana logika wywołana tuż przed zakończeniem programu; jeśli nie wiesz, na czym polega problem, nie możesz być pewien, że można go naprawić.
Shog9
1
Tj. Zainstaluj moduł obsługi sygnału, który odwija ​​niektóre dzienniki, które budujesz w czasie wykonywania, aby dowiedzieć się, gdzie program się zawiesił i, mam nadzieję, dlaczego.
Jaśniejsze
3
  1. Czy możesz uruchomić aplikację Java korzystającą z JNI z okna konsoli (uruchomić ją z wiersza poleceń Java), aby sprawdzić, czy istnieje raport o tym, co mogło zostać wykryte przed awarią JVM. Podczas uruchamiania bezpośrednio jako aplikacja do okna Java może brakować komunikatów, które pojawiłyby się, gdyby zamiast tego uruchomić z okna konsoli.

  2. Po drugie, czy możesz wyciąć implementację biblioteki DLL JNI, aby pokazać, że metody w bibliotece DLL są wprowadzane z JNI, zwracasz poprawnie itp.?

  3. Na wypadek, gdyby problem dotyczył nieprawidłowego użycia jednej z metod interfejsu JNI z kodu C ++, czy sprawdziłeś, czy niektóre proste przykłady JNI kompilują się i pracują z twoją instalacją? W szczególności myślę o użyciu metod interfejsu JNI do konwersji parametrów na natywne formaty C ++ i zamiany wyników funkcji na typy Java. Przydatne jest wytarcie tych, aby upewnić się, że konwersje danych działają i nie będziesz szaleć w wywołaniach typu COM w interfejsie JNI.

  4. Są inne rzeczy do sprawdzenia, ale trudno je zasugerować, nie wiedząc więcej o tym, jakie są twoje rodzime metody Java i jakie są ich implementacje JNI. Nie jest jasne, czy wyłapanie wyjątku z poziomu kodu C ++ jest związane z twoim problemem. (Możesz użyć interfejsu JNI, aby ponownie wprowadzić wyjątek jako Java, ale z tego, co podasz, nie wynika jasno, że to pomoże).

orcmid
źródło
2

Dla prawdziwego problemu związanego z niemożnością poprawnego debugowania programu korzystającego z JNI (lub błąd nie pojawia się podczas uruchamiania go w debugerze):

W takim przypadku często pomaga dodać owijki Java wokół wywołań JNI (tj. Wszystkie metody rodzime są prywatne, a metody publiczne w klasie wywołują je), które wykonują podstawowe sprawdzenie poprawności (sprawdzają, czy wszystkie „obiekty” są zwolnione, a „obiekty” nie są używane po zwolnieniu) lub synchronizacji (wystarczy zsynchronizować wszystkie metody z jednej biblioteki DLL do instancji pojedynczego obiektu). Pozwól metodom otoki Java zarejestrować błąd i zgłosić wyjątek.

Pomoże to często znaleźć prawdziwy błąd (który jest zaskakujący głównie w kodzie Java, który nie przestrzega semantyki wywoływanych funkcji, powodując nieprzyjemne podwójne zwolnienia lub podobne) łatwiej niż próba debugowania masowo równoległego programu Java w natywny debugger ...

Jeśli znasz przyczynę, zachowaj kod w metodach opakowania, które go unikają. Lepiej, aby Twoje metody pakowania zgłaszały wyjątki, niż kod JNI powoduje awarię maszyny wirtualnej ...

Mihi
źródło
1

To zależy od środowiska kompilatora. gcc ich nie łapie. Visual Studio i ostatni Borland, którego użyłem.

Wniosek dotyczący awarii jest taki, że zależy to od jakości środowiska programistycznego.

Specyfikacja C ++ mówi, że catch (...) musi przechwytywać wszelkie wyjątki, ale nie we wszystkich przypadkach.

Przynajmniej z tego, co próbowałem.

Jan
źródło
1

Być świadomym

try{
// ...
} catch (...) {
// ...
}

wyłapuje tylko wyjątki na poziomie języka, inne wyjątki / błędy niskiego poziomu, takie jak Access Violationi Segmentation Faultnie zostaną złapane.

muaz
źródło
Rzeczy takie jak błąd segmentacji nie są tak naprawdę wyjątkami, są sygnałami; dlatego nie można ich złapać jak typowych wyjątków. Jednak istnieją pewne obejścia, takie jak ten .
MAChitgarha