Widziałem co najmniej jedno wiarygodne źródło (klasa C ++, którą wybrałem), które zalecają, aby klasy wyjątków specyficzne dla aplikacji w C ++ dziedziczyły z std::exception
. Nie mam jasności co do zalet tego podejścia.
W C # powody dziedziczenia z ApplicationException
są jasne: dostajesz garść użytecznych metod, właściwości i konstruktorów i po prostu musisz dodać lub zastąpić to, czego potrzebujesz. Z std::exception
Wydaje się, że wszystko można dostać to what()
metoda aby przesłonić, które można równie dobrze tworzyć samemu.
Więc jakie są korzyści z używania std::exception
jako klasy bazowej dla mojej klasy wyjątków specyficznej dla aplikacji? Czy istnieją dobre powody, aby nie dziedziczyć std::exception
?
c++
exception
exception-handling
John M Gant
źródło
źródło
Odpowiedzi:
Główną zaletą jest to, że kod przy użyciu klasy nie muszą znać dokładny typ, co cię
throw
w nim, ale może po prostu . Edycja: jak zauważyli Martin i inni, tak naprawdę chcesz wyprowadzić z jednej z podklas zadeklarowanych w nagłówku.catch
std::exception
std::exception
<stdexcept>
źródło
std::exception
jest zdefiniowane w<exception>
nagłówku ( dowód ).-1
Problem
std::exception
polega na tym, że nie ma konstruktora (w wersjach zgodnych ze standardem), który akceptuje komunikat.W rezultacie wolę czerpać z
std::runtime_error
. Pochodzi z,std::exception
ale jego konstruktory umożliwiają przekazanie C-String lub astd::string
do konstruktora, który zostanie zwrócony (jako achar const*
), gdywhat()
zostanie wywołany.źródło
Przyczyną dziedziczenia
std::exception
jest „standardowa” klasa bazowa dla wyjątków, więc jest rzeczą naturalną, że na przykład inne osoby w zespole mogą się tego spodziewać i złapać podstawęstd::exception
.Jeśli szukasz wygody, możesz odziedziczyć po
std::runtime_error
tym, co zapewniastd::string
konstruktor.źródło
std::exception
to doskonały pomysł. Nie powinieneś dziedziczyć z więcej niż jednego.Kiedyś brałem udział w porządkowaniu dużej bazy kodu, w której poprzedni autorzy wrzucali ints, HRESULTS, std :: string, char *, losowe klasy ... wszędzie różne rzeczy; wystarczy podać typ i prawdopodobnie został gdzieś wyrzucony. I w ogóle nie ma wspólnej klasy bazowej. Uwierz mi, sprawy były znacznie uporządkowane, gdy doszliśmy do punktu, w którym wszyscy rzucani mieli wspólną podstawę, którą mogliśmy złapać i wiedzieli, że nic nie minie. Więc zrób sobie (i tym, którzy będą musieli dbać o Twój kod w przyszłości) przysługę i rób to w ten sposób od samego początku.
źródło
Powinieneś odziedziczyć po boost :: wyjątek . Zapewnia o wiele więcej funkcji i dobrze zrozumiałe sposoby przenoszenia dodatkowych danych ... oczywiście, jeśli nie używasz Boost , zignoruj tę sugestię.
źródło
Tak, powinieneś pochodzić z
std::exception
.Inni odpowiedzieli, że
std::exception
ma problem polegający na tym, że nie możesz przekazać do niego wiadomości tekstowej, jednak generalnie nie jest dobrym pomysłem próba sformatowania wiadomości użytkownika w miejscu rzutu. Zamiast tego użyj obiektu wyjątku, aby przetransportować wszystkie istotne informacje do miejsca łapania, które może następnie sformatować przyjazny dla użytkownika komunikat.źródło
std::exception
i przenosić informacje o wyjątku. Ale kto będzie odpowiedzialny za tworzenie / formatowanie komunikatu o błędzie?::what()
Funkcja lub coś innego?Powodem, dla którego możesz chcieć dziedziczyć z,
std::exception
jest to, że pozwala ci zgłosić wyjątek przechwycony zgodnie z tą klasą, tj .:źródło
Jest jeden problem z dziedziczeniem, o którym powinieneś wiedzieć, to cięcie obiektów. Podczas pisania
throw e;
wyrażenia rzutowego inicjuje obiekt tymczasowy, zwany obiektem wyjątku, którego typ jest określany przez usunięcie wszelkich kwalifikatorów cv najwyższego poziomu z typu statycznego operanduthrow
. To może nie być to, czego się spodziewasz. Przykład problemu, który możesz znaleźć tutaj .To nie jest argument przeciwko dziedziczeniu, to po prostu informacja „trzeba wiedzieć”.
źródło
throw;
jest ok, ale nie jest oczywiste, że należy coś takiego napisać.raise()
jako takie:virtual void raise() { throw *this; }
. Pamiętaj tylko, aby nadpisać to w każdym wyprowadzonym wyjątku.Różnica: std :: runtime_error vs std :: wyjątek ()
To, czy powinieneś po nim odziedziczyć, czy nie, zależy od Ciebie. Standard
std::exception
i jego standardowe potomkowie proponują jedną możliwą strukturę hierarchii wyjątków (podział nalogic_error
subhierarchy iruntime_error
subhierarchy) oraz jeden możliwy interfejs obiektu wyjątku. Jeśli Ci się podoba - wykorzystaj. Jeśli z jakiegoś powodu potrzebujesz czegoś innego - zdefiniuj własną strukturę wyjątków.źródło
Ponieważ język już zgłasza std :: wyjątek, i tak musisz go przechwycić, aby zapewnić przyzwoite raportowanie błędów. Równie dobrze możesz użyć tego samego haczyka we wszystkich nieoczekiwanych wyjątkach. Ponadto prawie każda biblioteka, która zgłasza wyjątki, mogłaby je wyprowadzić ze std :: wyjątek.
Innymi słowy, to albo
lub
A druga opcja jest zdecydowanie lepsza.
źródło
Jeśli wynikają wszystkie możliwe wyjątki
std::exception
, blok catch można łatwocatch(std::exception & e)
i pewnie przechwycić wszystko.Po przechwyceniu wyjątku możesz użyć tej
what
metody, aby uzyskać więcej informacji. C ++ nie obsługuje pisania typu kaczego, więc inna klasa zwhat
metodą wymagałaby innego catch i innego kodu, aby jej użyć.źródło
Pierwszym pytaniem jest, czy pochodzić z dowolnego standardowego typu wyjątku, czy nie. W ten sposób włącza się jeden program obsługi wyjątków dla wszystkich wyjątków bibliotek standardowych i własnych, ale zachęca również do takich programów obsługi typu catch-them-all. Problem polega na tym, że należy wychwytywać tylko wyjątki, które znamy. Na przykład w funkcji main () przechwytywanie wszystkich wyjątków std :: jest prawdopodobnie dobrą rzeczą, jeśli łańcuch what () zostanie zarejestrowany w ostateczności przed zakończeniem. Jednak gdzie indziej nie jest to dobry pomysł.
Kiedy już zdecydujesz, czy wyprowadzić ze standardowego typu wyjątku, czy nie, pojawia się pytanie, który powinien być podstawą. Jeśli Twoja aplikacja nie potrzebuje i18n, możesz pomyśleć, że formatowanie wiadomości w miejscu wywołania jest równie dobre, jak zapisywanie informacji i generowanie wiadomości w miejscu wywołania. Problem polega na tym, że sformatowana wiadomość może nie być potrzebna. Lepiej jest użyć leniwego schematu generowania wiadomości - być może z wstępnie przydzieloną pamięcią. Następnie, jeśli wiadomość jest potrzebna, zostanie wygenerowana przy dostępie (i prawdopodobnie buforowana w obiekcie wyjątku). W związku z tym, jeśli komunikat jest generowany podczas rzucania, wówczas jako klasa podstawowa wymagana jest podstawowa klasa std :: wyjątek, taka jak std :: runtime_error. Jeśli wiadomość jest generowana leniwie, wówczas odpowiednią podstawą jest std ::ception.
źródło
Innym powodem wyjątków dla podklas jest lepszy aspekt projektowania podczas pracy z dużymi zamkniętymi systemami. Możesz go ponownie użyć do takich rzeczy, jak komunikaty walidacyjne, zapytania użytkowników, krytyczne błędy kontrolera i tak dalej. Zamiast przepisać lub ponownie podłączyć wszystkie swoje komunikaty walidacyjne, takie jak komunikaty, możesz po prostu „przechwycić” je w głównym pliku źródłowym, ale wyrzucić błąd w dowolnym miejscu w całym zestawie klas.
Np. krytyczny wyjątek zakończy działanie programu, błąd walidacji wyczyści tylko stos, a zapytanie użytkownika zada pytanie użytkownikowi końcowemu.
Zrobienie tego w ten sposób oznacza również, że możesz ponownie użyć tych samych klas, ale na różnych interfejsach. np. aplikacja Windows może korzystać z okna komunikatu, usługa sieciowa pokaże html, a system raportowania go zaloguje i tak dalej.
źródło
Chociaż to pytanie jest dość stare i zostało już wiele odpowiedzi, chcę tylko dodać uwagę na temat poprawnej obsługi wyjątków w C ++ 11, ponieważ ciągle brakuje mi tego w dyskusjach na temat wyjątków:
Użyj
std::nested_exception
istd::throw_with_nested
Jest to opisane w StackOverflow tutaj i tutaj , w jaki sposób można uzyskać śledzenie wstecz dla wyjątków w kodzie bez potrzeby debugera lub kłopotliwego rejestrowania, po prostu pisząc odpowiednią procedurę obsługi wyjątków, która ponownie wyrzuci zagnieżdżone wyjątki.
Ponieważ możesz to zrobić z każdą pochodną klasą wyjątków, możesz dodać wiele informacji do takiego śledzenia wstecznego! Możesz również rzucić okiem na moje MWE na GitHub , gdzie ślad cofania wyglądałby mniej więcej tak:
Nie musisz nawet tworzyć podklasy
std::runtime_error
, aby uzyskać dużo informacji, gdy zostanie zgłoszony wyjątek.Jedyną korzyścią, jaką widzę w tworzeniu podklas (zamiast po prostu używać
std::runtime_error
), jest to, że program obsługi wyjątków może przechwycić niestandardowy wyjątek i zrobić coś specjalnego. Na przykład:źródło