Czy powinienem używać specyfikatora wyjątku w C ++?

123

W C ++ można określić, że funkcja może, ale nie musi, zgłaszać wyjątek przy użyciu specyfikatora wyjątku. Na przykład:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

Wątpię, czy faktycznie ich używam z następujących powodów:

  1. Kompilator tak naprawdę nie narzuca specyfikatorów wyjątków w żaden rygorystyczny sposób, więc korzyści nie są duże. Idealnie byłoby, gdybyś chciał otrzymać błąd kompilacji.
  2. Jeśli funkcja narusza specyfikator wyjątku, myślę, że standardowym zachowaniem jest zakończenie programu.
  3. W VS.Net traktuje rzut (X) jako rzut (...), więc trzymanie się standardu nie jest mocne.

Czy uważasz, że należy używać specyfikatorów wyjątków?
Odpowiedz „tak” lub „nie” i podaj powody uzasadniające odpowiedź.

1800 INFORMACJE
źródło
7
„throw (...)” nie jest standardowym językiem C ++. Uważam, że jest to rozszerzenie w niektórych kompilatorach i ogólnie ma to samo znaczenie, co brak specyfikacji wyjątku.
Richard Corden,

Odpowiedzi:

97

Nie.

Oto kilka przykładów, dlaczego:

  1. Nie można zapisać kodu szablonu ze specyfikacjami wyjątków,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

    Kopie mogą zostać rzucone, przekazanie parametru może zgłosić i x()może zgłosić nieznany wyjątek.

  2. Specyfikacje wyjątków zwykle zabraniają rozszerzalności.

    virtual void open() throw( FileNotFound );

    może ewoluować w

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    Naprawdę możesz to napisać jako

    throw( ... )

    Pierwsza nie jest rozszerzalna, druga jest zbyt ambitna, a trzecia jest naprawdę tym, co masz na myśli, pisząc funkcje wirtualne.

  3. Starszy kod

    Kiedy piszesz kod, który opiera się na innej bibliotece, tak naprawdę nie wiesz, co może zrobić, gdy coś pójdzie nie tak.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }

    gkończy się, gdy lib_f()rzuca. To (w większości przypadków) nie jest to, czego naprawdę chcesz. std::terminate()nigdy nie należy nazywać. Zawsze lepiej jest pozwolić aplikacji zawiesić się z nieobsługiwanym wyjątkiem, z którego można pobrać ślad stosu, niż cicho / gwałtownie umrzeć.

  4. Napisz kod, który zwraca typowe błędy i wyrzuca w wyjątkowych sytuacjach.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }

Niemniej jednak, gdy Twoja biblioteka po prostu zgłasza własne wyjątki, możesz użyć specyfikacji wyjątków, aby określić swój zamiar.

Krzysztof
źródło
1
w 3, technicznie byłoby to std :: nieoczekiwany nie std :: terminate. Ale kiedy ta funkcja jest wywoływana, domyślnie wywołuje abort (). Generuje to zrzut rdzenia. Jak to jest gorsze niż nieobsługiwany wyjątek? (co w zasadzie robi to samo)
Greg Rogers
6
@Greg Rogers: Niezaładowany wyjątek nadal powoduje rozwijanie stosu. Oznacza to, że zostaną wywołane destruktory. A w tych destruktorach można wiele zrobić, na przykład: Zasoby poprawnie zwolnione, logi poprawnie napisane, innym procesom zostanie poinformowany, że bieżący proces ulega awarii itp. Podsumowując, to RAII.
paercebal,
Pominięto: To skutecznie opakowuje wszystko w try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]konstrukcję, niezależnie od tego, czy chcesz tam blokować try.
David Thornley
4
@paercebal To jest nieprawidłowe. Jest to implementacja zdefiniowana, czy destruktory są uruchamiane w przypadku nieprzechwyconych wyjątków. Większość środowisk nie rozwija destruktorów stosu / uruchamiania, jeśli wyjątek nie zostanie przechwycony. Jeśli chcesz zapewnić przenośne działanie destruktorów, nawet gdy wyjątek jest zgłaszany i nie jest obsługiwany (ma to wątpliwą wartość), musisz napisać swój kod w stylutry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo
Zawsze lepiej jest pozwolić aplikacji zawiesić się z nieobsługiwanym wyjątkiem, z którego można pobrać ślad stosu, niż cicho / gwałtownie umrzeć. ” W jaki sposób „awaria” może być lepsza niż czyste wywołanie terminate()? Dlaczego po prostu nie zadzwonisz abort()?
curiousguy
42

Unikaj specyfikacji wyjątków w C ++. Powody, które podajesz w swoim pytaniu, to całkiem dobry początek.

Zobacz „Pragmatic Look at Exception Specifications” Herba Suttera .

Michael Burr
źródło
3
@awoodland: użycie „dynamicznych specyfikacji wyjątków” ( throw(optional-type-id-list)) jest przestarzałe w C ++ 11. Nadal są w standardzie, ale wydaje mi się, że wysłano ostrzeżenie, że należy dokładnie rozważyć ich użycie. C ++ 11 dodaje noexceptspecyfikację i operator. Nie znam wystarczająco dużo szczegółów, noexceptaby to skomentować. Ten artykuł wydaje się być dość szczegółowy: akrzemi1.wordpress.com/2011/06/10/using-noexcept A Dietmar Kühl opublikował artykuł w Overload Journal z czerwca 2011 r .: accu.org/var/uploads/journals/overload103.pdf
Michael Burr
@MichaelBurr Only throw(something)jest uważany za bezużyteczny i zły pomysł. throw()jest przydatny.
curiousguy
Oto, co wielu ludzi myśli, że specyfikacje wyjątków robią: bullet Gwarancja, że ​​funkcje będą generować tylko wymienione wyjątki (prawdopodobnie żadne). Bullet Włącz optymalizacje kompilatora w oparciu o wiedzę, że zostaną wyrzucone tylko wymienione wyjątki (prawdopodobnie żadne). Powyższe oczekiwania to: znowu, łudząco blisko bycia poprawnymi „Nie, powyższe oczekiwania są absolutnie poprawne.
ciekawy facet
14

Wydaje mi się, że standardowo poza konwencją (dla C ++)
specyfikatory wyjątków były eksperymentem w standardzie C ++, który w większości się nie powiódł.
Wyjątkiem jest to, że specyfikator no throw jest przydatny, ale należy również wewnętrznie dodać odpowiedni blok try catch, aby upewnić się, że kod pasuje do specyfikatora. Herb Sutter ma stronę na ten temat. Gniew 82

Dodatkowo myślę, że warto opisać Gwarancje Wyjątków.

Są to w zasadzie dokumentacja opisująca, w jaki sposób na stan obiektu wpływają wyjątki uciekające przed metodą na tym obiekcie. Niestety, kompilator nie wymusza ich ani nie wspomina o nich w inny sposób.
Wzmocnienie i wyjątki

Gwarancje wyjątków

Bez gwarancji:

Nie ma gwarancji co do stanu obiektu po wyjściu wyjątku z metody
W takich sytuacjach obiekt nie powinien być dłużej używany.

Gwarancja podstawowa:

W prawie wszystkich sytuacjach powinna to być minimalna gwarancja, jaką zapewnia metoda.
Gwarantuje to, że stan obiektu jest dobrze zdefiniowany i nadal można go używać konsekwentnie.

Silna gwarancja: (inaczej gwarancja transakcyjna)

Gwarantuje to, że metoda zakończy się pomyślnie
lub zostanie zgłoszony wyjątek, a stan obiektów nie ulegnie zmianie.

Gwarancja braku rzutu:

Metoda gwarantuje, że żadne wyjątki nie będą propagowane z metody.
Wszystkie destruktory powinny dawać taką gwarancję.
| Uwaga: jeśli wyjątek ucieka przed destruktorem, podczas gdy wyjątek jest już propagowany
| aplikacja zostanie zakończona

Martin York
źródło
Gwarancje są czymś, co każdy programista C ++ musi znać, ale wydaje mi się, że nie są one związane ze specyfikacjami wyjątków.
David Thornley,
1
@David Thornley: Widzę gwarancje jako specyfikacje wyjątków (tj. Metoda z silnym G nie może wywołać metody z podstawowym G bez ochrony). Niestety nie jestem pewien, czy są one wystarczająco dobrze zdefiniowane, aby można je było egzekwować w użyteczny sposób przez kompilator.
Martin York
" Oto, co wiele osób myśli, że specyfikacje wyjątków robią: - Gwarancja, że ​​funkcje będą generować tylko wymienione wyjątki (prawdopodobnie żadne). - Włącz optymalizacje kompilatora na podstawie wiedzy, że zostaną wyrzucone tylko wymienione wyjątki (prawdopodobnie żadne). Powyższe oczekiwania to: znowu, łudząco blisko bycia poprawnym. ”Właściwie oba mają dokładnie rację.
curiousguy
@curiousguy. W ten sposób robi to Java, ponieważ sprawdzanie jest wymuszane w czasie kompilacji. C ++ to testy uruchomieniowe. Tak: Guarantee that functions will only throw listed exceptions (possibly none). Nie prawda. Gwarantuje tylko, że jeśli funkcja zgłosi te wyjątki, aplikacja zakończy działanie. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownNie mogę tego zrobić. Ponieważ właśnie wskazałem, nie możesz zagwarantować, że wyjątek nie zostanie zgłoszony.
Martin York,
1
@LokiAstari "W ten sposób Java to robi, ponieważ kontrole są wymuszane w czasie kompilacji_" Specyfikacja wyjątku Java to eksperyment, który się nie powiódł. Java nie ma najbardziej użytecznej specyfikacji wyjątku: throw()(nie zgłasza). " Gwarantuje tylko, że jeśli funkcja zgłosi te wyjątki, aplikacja zakończy działanie. „ Nie ”nie prawda” oznacza prawdę. W C ++ nie ma gwarancji, że funkcja terminate()nigdy nie wywoła. „ Ponieważ właśnie wskazałem, nie możesz zagwarantować, że wyjątek nie zostanie zgłoszony ” Gwarantujesz, że funkcja nie zgłosi. Właśnie tego potrzebujesz.
ciekawy facet
8

gcc będzie emitować ostrzeżenia, gdy naruszysz specyfikacje wyjątków. To, co robię, to używać makr do używania specyfikacji wyjątków tylko w trybie "lint", kompilując się wyraźnie w celu sprawdzenia, czy wyjątki są zgodne z moją dokumentacją.

Jeremy
źródło
7

Jedynym użytecznym specyfikatorem wyjątku jest „throw ()”, na przykład „nie rzuca”.

Harold Ekstrom
źródło
2
Czy możesz dodać powód, dla którego jest to przydatne?
buti-oxa
2
dlaczego to nie jest przydatne? nie ma nic bardziej użytecznego niż wiedza, że ​​jakaś funkcja nie zacznie rzucać wyjątków w lewo i w prawo.
Matt Joiner
1
Proszę zapoznać się z dyskusją na temat Herb Suttera, do której odwołuje się odpowiedź Michaela Burra, aby uzyskać szczegółowe wyjaśnienie.
Harold Ekstrom
4

Specyfikacje wyjątków nie są cudownie użytecznymi narzędziami w C ++. Jednak jest / jest / dobry dla nich użytek, jeśli połączymy je z std :: nieoczekiwany.

To, co robię w niektórych projektach, to kod ze specyfikacjami wyjątków, a następnie wywołanie metody set_uknown () z funkcją, która wyrzuci specjalny wyjątek mojego własnego projektu. Ten wyjątek, po skonstruowaniu, pobiera ślad wsteczny (w sposób specyficzny dla platformy) i pochodzi z std :: bad_exception (aby umożliwić propagację w razie potrzeby). Jeśli wywołuje wywołanie terminate (), jak to zwykle bywa, ślad wstecz jest wypisywany przez what () (jak również oryginalny wyjątek, który go spowodował; nie jest to trudne do znalezienia), więc otrzymuję informacje o tym, gdzie znajdował się mój kontrakt naruszony, na przykład zgłoszony nieoczekiwany wyjątek biblioteki.

Jeśli to zrobię, nigdy nie zezwalam na propagację wyjątków bibliotek (z wyjątkiem tych std) i wyprowadzam wszystkie moje wyjątki z std :: wyjątek. Jeśli biblioteka zdecyduje się rzucić, złapię i przekonwertuję na swoją własną hierarchię, pozwalając mi zawsze kontrolować kod. Funkcje oparte na szablonach, które wywołują funkcje zależne, powinny unikać specyfikacji wyjątków z oczywistych powodów; ale i tak rzadko zdarza się mieć interfejs funkcji oparty na szablonach z kodem biblioteki (a kilka bibliotek naprawdę używa szablonów w użyteczny sposób).

coppro
źródło
3

Jeśli piszesz kod, który będzie używany przez ludzi, którzy woleliby przyjrzeć się deklaracji funkcji niż jakimkolwiek komentarzom wokół niej, wówczas specyfikacja powie im, które wyjątki mogą chcieć przechwycić.

W przeciwnym razie nie uważam za szczególnie przydatne używanie czegokolwiek poza throw()wskazaniem, że nie zgłasza żadnych wyjątków.

Branan
źródło
3

Nie. Jeśli ich użyjesz i zostanie wyrzucony wyjątek, którego nie określiłeś, ani przez swój kod, ani przez kod wywoływany przez Twój kod, wtedy domyślnym zachowaniem jest natychmiastowe zakończenie programu.

Uważam również, że ich użycie zostało uznane za przestarzałe w aktualnych wersjach standardu C ++ 0x.

Ferruccio
źródło
2

Generalnie nie użyłbym specyfikatorów wyjątków. Jednak w przypadkach, gdy jakikolwiek inny wyjątek miałby pochodzić od danej funkcji, program ostatecznie nie byłby w stanie skorygować , może być przydatny. We wszystkich przypadkach upewnij się, że jasno udokumentowałeś, jakich wyjątków można oczekiwać od tej funkcji.

Tak, oczekiwanym zachowaniem niewyszczególnionego wyjątku generowanego przez funkcję ze specyfikatorami wyjątków jest wywołanie terminate ().

Zwrócę również uwagę, że Scott Meyers porusza ten temat w bardziej efektywnym języku C ++. Jego efektywne C ++ i bardziej efektywne C ++ są wysoce polecanymi książkami.

Kris Kumler
źródło
2

Tak, jeśli interesujesz się wewnętrzną dokumentacją. A może napisanie biblioteki, z której będą korzystać inni, aby mogli powiedzieć, co się stanie, bez zapoznania się z dokumentacją. Rzucanie lub nie wyrzucanie można uznać za część interfejsu API, prawie jak wartość zwracana.

Zgadzam się, nie są one tak naprawdę przydatne do wymuszania poprawności stylu Java w kompilatorze, ale jest lepsze niż nic lub przypadkowe komentarze.

user10392
źródło
2

Mogą być przydatne do testowania jednostkowego, aby podczas pisania testów wiedzieć, czego oczekiwać, że funkcja wyrzuci, gdy się nie powiedzie, ale nie ma ich wymuszania w kompilatorze. Myślę, że to dodatkowy kod, który nie jest potrzebny w C ++. Zawsze wybierasz wszystko, czego powinieneś być pewien, to przestrzeganie tego samego standardu kodowania w całym projekcie i członkach zespołu, dzięki czemu kod pozostaje czytelny.

Dziwny
źródło
0

Z artykułu:

http://www.boost.org/community/exception_safety.html

„Powszechnie wiadomo, że nie jest możliwe napisanie bezpiecznego kontenera ogólnego”. Twierdzenie to jest często słyszane w odniesieniu do artykułu Toma Cargilla [4], w którym bada on problem bezpieczeństwa wyjątków dla ogólnego szablonu stosu. W swoim artykule firma Cargill stawia wiele przydatnych pytań, ale niestety nie przedstawia rozwiązania swojego problemu1. Na zakończenie sugeruje, że rozwiązanie może nie być możliwe. Niestety, wielu czytało jego artykuł jako „dowód” na te spekulacje. Odkąd został opublikowany, pojawiło się wiele przykładów bezpiecznych dla wyjątków komponentów ogólnych, między innymi kontenerów bibliotek standardowych C ++.

I rzeczywiście, mogę wymyślić sposoby uczynienia wyjątków klas szablonów bezpiecznymi. Jeśli nie masz kontroli nad wszystkimi podklasami, i tak możesz mieć problem. Aby to zrobić, można utworzyć w klasach typedefs, które definiują wyjątki wyrzucane przez różne klasy szablonów. Uważam, że problem polega na tym, że jak zwykle rozwiążemy to później, w przeciwieństwie do projektowania od samego początku, i myślę, że to ten koszt jest prawdziwą przeszkodą.

Marius
źródło
-2

Specyfikacje wyjątków = bzdury, zapytaj dowolnego programistę Java w wieku powyżej 30 lat

Greg Dean
źródło
8
programiści java w wieku powyżej 30 lat powinni czuć się źle, że nie radzą sobie z C, nie mają wymówki, by używać javy.
Matt Joiner