Ponad 10 lat temu w szkole uczyli cię używania specyfikatorów wyjątków. Ponieważ moje doświadczenie jest jednym z nich, programiści Torvaldish C, którzy uparcie unikają C ++, chyba że są do tego zmuszeni, trafiam tylko do C ++ sporadycznie, a kiedy to robię, nadal używam specyfikatorów wyjątków, ponieważ tego się nauczono.
Jednak większość programistów C ++ marszczy brwi w stosunku do specyfikatorów wyjątków. Przeczytałem debatę i argumenty różnych guru C ++, takich jak te . O ile rozumiem, sprowadza się do trzech rzeczy:
- Specyfikatory wyjątków używają systemu typów, który jest niezgodny z resztą języka („system typów cieni”).
- Jeśli twoja funkcja ze specyfikatorem wyjątku wyrzuci cokolwiek innego niż to, co określiłeś, program zostanie zakończony w zły, nieoczekiwany sposób.
- Specyfikatory wyjątków zostaną usunięte w nadchodzącym standardzie C ++.
Czy coś tu brakuje, czy to z tych wszystkich powodów?
Moje własne opinie:
Odnośnie 1): Więc co. C ++ jest prawdopodobnie najbardziej niespójnym językiem programowania, jaki kiedykolwiek stworzono, pod względem składniowym. Mamy makra, goto / etykiety, hordę (skarb?) O zachowaniu niezdefiniowanym / nieokreślonym / implementacji, źle zdefiniowane typy liczb całkowitych, wszystkie reguły promocji typu domyślnego, słowa kluczowe ze specjalnymi przypadkami, takie jak przyjaciel, auto , zarejestruj się, wyraźne ... I tak dalej. Ktoś prawdopodobnie mógłby napisać kilka grubych książek o całej dziwności w C / C ++. Dlaczego więc ludzie reagują na tę szczególną niekonsekwencję, która jest drobną wadą w porównaniu z wieloma innymi znacznie bardziej niebezpiecznymi cechami języka?
Odnośnie 2): Czy to nie moja własna odpowiedzialność? Jest tak wiele innych sposobów, aby napisać fatalny błąd w C ++, dlaczego ten konkretny przypadek jest jeszcze gorszy? Zamiast pisać, throw(int)
a następnie rzucać Crash_t, mogę równie dobrze twierdzić, że moja funkcja zwraca wskaźnik do int, a następnie utworzyć dziki, wyraźny typecast i zwrócić wskaźnik do Crash_t. Duchem C / C ++ zawsze było pozostawienie większości odpowiedzialności programistom.
A co z zaletami? Najbardziej oczywiste jest to, że jeśli twoja funkcja próbuje jawnie wyrzucić dowolny typ inny niż określony, kompilator wyświetli błąd. Uważam, że standard jest jasny w tym zakresie (?). Błędy będą występować tylko wtedy, gdy funkcja wywołuje inne funkcje, które z kolei generują niewłaściwy typ.
Pochodząc ze świata deterministycznych, osadzonych programów C, z pewnością wolałbym wiedzieć dokładnie, co rzuci na mnie funkcja. Jeśli jest coś w tym języku, dlaczego tego nie użyć? Alternatywami wydają się być:
void func() throw(Egg_t);
i
void func(); // This function throws an Egg_t
Myślę, że istnieje duża szansa, że osoba dzwoniąca zignoruje / zapomni wdrożyć try-catch w drugim przypadku, a mniej w pierwszym przypadku.
Jak rozumiem, jeśli jedna z tych dwóch form zdecyduje się nagle rzucić inny rodzaj wyjątku, program się zawiesi. W pierwszym przypadku, ponieważ nie wolno rzucać innego wyjątku, w drugim przypadku, ponieważ nikt nie spodziewał się, że wyrzuci SpanishInquisition_t, a zatem to wyrażenie nie jest rejestrowane tam, gdzie powinno być.
W przypadku tego ostatniego, złapanie w ostateczności (...) na najwyższym poziomie programu nie wydaje się wcale lepsze niż awaria programu: „Hej, gdzieś w twoim programie coś rzuciło dziwny, nieobsługiwany wyjątek . ” Nie możesz odzyskać programu, gdy jesteś tak daleko od miejsca zgłoszenia wyjątku, jedyne, co możesz zrobić, to wyjść z programu.
A z punktu widzenia użytkownika nie obchodzi ich to, że otrzymają z systemu operacyjnego złą wiadomość z komunikatem „Program zakończony. Blablabla pod adresem 0x12345” lub złą wiadomość z twojego programu z informacją „Nieobsługiwany wyjątek: mojaklasa. func.something ”. Błąd jest nadal obecny.
W nadchodzącym standardzie C ++ nie będę miał innej opcji, jak zrezygnować ze specyfikatorów wyjątków. Ale wolałbym raczej wysłuchać solidnego argumentu, dlaczego są źli, niż „Jego Świątobliwość to stwierdził i tak jest”. Być może jest więcej argumentów przeciwko nim niż te, które wymieniłem, a może jest ich więcej, niż zdaję sobie sprawę?
Odpowiedzi:
Specyfikacje wyjątków są złe, ponieważ są słabo egzekwowane, a zatem nie osiągają zbyt wiele, a także są złe, ponieważ zmuszają środowisko wykonawcze do sprawdzenia nieoczekiwanych wyjątków, aby mogły zakończyć działanie (), zamiast wywoływać UB , może to zmarnować znaczną część wydajności.
Podsumowując, specyfikacje wyjątków nie są wystarczająco egzekwowane w języku, aby kod stał się bezpieczniejszy, a wdrożenie ich zgodnie ze specyfikacją spowodowało duży spadek wydajności.
źródło
Jednym z powodów, dla których nikt ich nie używa, jest to, że twoje podstawowe założenie jest błędne:
Ten program kompiluje się bez błędów i ostrzeżeń .
Ponadto, mimo że wyjątek zostałby wychwycony , program nadal się kończy.
Edycja: aby odpowiedzieć na pytanie w pytaniu, catch (...) (lub lepiej, catch (std :: exception &) lub inna klasa podstawowa, a następnie catch (...)) jest nadal przydatne, nawet jeśli nie dokładnie wiedzieć, co poszło nie tak.
Weź pod uwagę, że użytkownik nacisnął przycisk menu „Zapisz”. Moduł obsługi przycisku menu zaprasza aplikację do zapisania. Może się to nie udać z wielu powodów: plik znajdował się w zasobie sieciowym, który zniknął, był plik tylko do odczytu i nie można go zapisać itp. Ale program obsługi nie obchodzi, dlaczego coś się nie powiedzie; obchodzi go tylko to, czy mu się to udało, czy nie. Jeśli się powiedzie, wszystko jest dobrze. Jeśli się nie powiedzie, może poinformować użytkownika. Dokładny charakter wyjątku jest nieistotny. Ponadto, jeśli napiszesz odpowiedni, bezpieczny dla wyjątków kod, oznacza to, że każdy taki błąd może rozprzestrzeniać się w twoim systemie bez obniżania go - nawet dla kodu, który nie przejmuje się błędem. Ułatwia to rozbudowę systemu. Na przykład teraz oszczędzasz przez połączenie z bazą danych.
źródło
Ten wywiad z Anders Hejlsberg jest dość sławny. W nim wyjaśnia, dlaczego zespół projektowy C # odrzucił sprawdzone wyjątki. Krótko mówiąc, istnieją dwa główne powody: możliwość aktualizacji i skalowalność.
Wiem, że OP koncentruje się na C ++, podczas gdy Hejlsberg omawia C #, ale kwestie, które wysuwa Hejlsberg, doskonale pasują również do C ++.
źródło