Czy należy wywodzić / dziedziczyć po std :: wyjatku?

15

Projektując moją pierwszą „poważną” bibliotekę C ++, zadaję sobie pytanie:

Czy to dobry styl czerpać wyjątki std::exceptioni to jest potomstwo ?!

Nawet po przeczytaniu

Wciąż nie jestem pewien. Ponieważ, poza powszechną (ale może nie dobrą) praktyką, jako użytkownik biblioteki zakładam, że funkcja biblioteczna rzucałaby std::exceptiontylko wtedy, gdy standardowe funkcje biblioteczne zawiodły w implementacji biblioteki, i nic na to nie poradzi. Ale mimo to, pisząc kod aplikacji, jest to dla mnie bardzo wygodne, a także IMHO dobrze wygląda po prostu rzucić std::runtime_error. Również moi użytkownicy mogą polegać na zdefiniowanym minimalnym interfejsie, takim jak what()lub kody.

I na przykład mój użytkownik podaje błędne argumenty, co byłoby wygodniejsze, niż rzucić std::invalid_argument, prawda? Tak więc w połączeniu z powszechnym użyciem std :: wyjątku widzę w innym kodzie: Dlaczego nie pójść jeszcze dalej i wywodzić się z niestandardowej klasy wyjątków (np. Lib_foo_exception), a także z std::exception.

Myśli?

Superlokkus
źródło
Nie jestem pewien, czy podążam. Tylko dlatego, że dziedziczą std::exceptionnie znaczy rzucaćstd::exception . Ponadto std::runtime_errordziedziczy std::exceptionon po pierwsze, a what()metoda pochodzi std::exception, a nie std::runtime_error. I zdecydowanie powinieneś stworzyć własne klasy wyjątków zamiast generować wyjątki ogólne, takie jak std::runtime_error.
Vincent Savard
3
Różnica polega na tym, że kiedy moja lib_foo_exceptionklasa wywodzi się std::exception, użytkownik biblioteki łapałby lib_foo_exceptionpo prostu łapiąc std::exception, oprócz tego, kiedy łapał tylko bibliotekę. Więc mógłbym również zapytać, czy klasa główna wyjątków mojej biblioteki dziedziczy po std :: wyjątek .
Superlokkus
3
@LightnessRacesinOrbit Mam na myśli „... oprócz”, na przykład „Ile sposobów można złapać lib_foo_exception?” Dzięki dziedziczeniu std::exceptionmożesz to zrobić przez catch(std::exception)LUB catch(lib_foo_exception). Bez wywodzenia std::exceptionzłapałbyś go wtedy i tylko wtedy , gdycatch(lib_foo_exception) .
Superlokkus
2
@Superlokkus: Trochę ignorujemy catch(...). Jest tak, ponieważ język uwzględnia przypadek, który rozważasz (i biblioteki „źle zachowujące się”), ale to nie jest najlepsza współczesna praktyka.
Wyścigi lekkości z Monicą
1
Wiele konstrukcji obsługi wyjątków w C ++ ma tendencję do zachęcania do grubszych, bardziej ogólnych catchwitryn, a także bardziej zgrubnych transakcji, które modelują operacje użytkownika końcowego. Jeśli porównasz to z językami, które nie promują idei uogólnionego łapania std::exception&, np. Często mają o wiele więcej kodu z try/catchblokami pośredniczącymi dotyczącymi bardzo specyficznych błędów, co nieco zmniejsza ogólną obsługę wyjątków, gdy zaczyna się umieszczać znacznie większy nacisk na ręczne zarządzanie błędami, a także na wszystkie różne błędy, które mogą wystąpić.

Odpowiedzi:

29

Wszystkie wyjątki powinny dziedziczyć std::exception.

Załóżmy na przykład, że muszę zadzwonić ComplexOperationThatCouldFailABunchOfWays()i chcę poradzić sobie z wyjątkami, które może wywołać . Jeśli wszystko dziedziczy std::exception, jest to łatwe. Potrzebuję tylko jednego catchbloku i mam standardowy interfejs ( what()) do uzyskiwania szczegółów.

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

Jeśli wyjątki NIE dziedziczą std::exception, staje się to znacznie brzydsze:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

Odnośnie do rzucania runtime_errorlub invalid_argumenttworzenia własnych std::exceptionpodklas do rzucania: Moją ogólną zasadą jest wprowadzanie nowej podklasy za każdym razem, gdy potrzebuję obsługiwać określony typ błędu inaczej niż inne błędy (tj. Za każdym razem, gdy potrzebuję osobnego catchbloku).

  • Jeśli wprowadzę nową podklasę wyjątku dla każdego możliwego rodzaju błędu, nawet jeśli nie będę musiał obsługiwać ich osobno, to zwiększy to mnożenie klas.
  • Jeśli ponownie użyję istniejących podklas, aby oznaczać coś konkretnego (tj. Jeśli runtime_error wyrzucony tutaj oznacza coś innego niż ogólny błąd czasu wykonywania), wówczas ryzykuję konflikt z innymi zastosowaniami istniejącej podklasy.
  • Jeśli ja nie muszę specjalnie obsługiwać błędu i jeśli zgłaszany przeze mnie błąd dokładnie odpowiada jednemu z błędów istniejącej biblioteki standardowej (np. invalid_argument), To ponownie używam istniejącej klasy. Po prostu nie widzę większych korzyści z dodania nowej klasy w tym przypadku. (Podstawowe wytyczne C ++ nie zgadzają się ze mną tutaj - zalecają zawsze używanie własnych klas).

W ++ Wytyczne C rdzeniowe mają dalej dyskusja i przykłady.

Josh Kelley
źródło
Wszystkie te znaki handlowe! C ++ jest dziwny.
SuperJedi224,
2
@ SuperJedi224 i wszystkie inne litery! Angielski jest dziwny.
johannes,
To uzasadnienie nie ma dla mnie sensu. Czy nie po to jest catch (...)(z dosłowną elipsą)?
Maks.
1
@Maxpm catch (...)przydaje się tylko wtedy, gdy nie musisz nic robić z tym, co zostało rzucone. Jeśli chcesz coś zrobić - np. Wyświetlić lub zapisać konkretny komunikat o błędzie, jak w moim przykładzie - musisz wiedzieć, co to jest.
Josh Kelley,
9

Jako użytkownik biblioteki zakładałbym, że funkcja biblioteczna zgłasza wyjątki std :: tylko wtedy, gdy standardowe funkcje biblioteczne zawiodły w implementacji biblioteki i nie może nic z tym zrobić

To błędne założenie.

Standardowe typy wyjątków są przewidziane dla „powszechniejszych” zastosowań. Nie są przeznaczone wyłącznie do tego używania przez bibliotekę standardową.

Tak, spraw, aby wszystko ostatecznie odziedziczyło std::exception. Często wymaga to dziedziczenia z std::runtime_errorlub std::logic_error. Cokolwiek jest odpowiednie dla wdrażanej klasy wyjątków.

Jest to oczywiście subiektywne - kilka popularnych bibliotek całkowicie ignoruje standardowe typy wyjątków, prawdopodobnie w celu oddzielenia bibliotek od standardowej biblioteki. Osobiście uważam, że to wyjątkowo samolubne! Sprawia, że ​​łapanie wyjątków jest o wiele trudniejsze.

Mówiąc osobiście, często po prostu rzucam std::runtime_errori skończę z tym. Ale to prowadzi do dyskusji na temat tego, jak szczegółowe jest tworzenie klas wyjątków, o co nie pytasz.

Lekkość Wyścigi z Moniką
źródło