Obsługa wyjątków w C ++ jest ograniczona do try / throw / catch. W przeciwieństwie do Object Pascal, Java, C # i Python, nawet w C ++ 11 finally
konstrukcja nie została zaimplementowana.
Widziałem okropnie dużo literatury C ++ omawiającej „bezpieczny kod wyjątku”. Lippman pisze, że bezpieczny kod wyjątku jest ważnym, ale zaawansowanym, trudnym tematem, wykraczającym poza zakres jego Primer - co wydaje się sugerować, że bezpieczny kod nie jest fundamentalny dla C ++. Herb Sutter poświęca temu tematowi 10 rozdziałów w swoim wyjątkowym C ++!
Wydaje mi się jednak, że wiele problemów napotkanych podczas próby napisania „kodu bezpiecznego wyjątku” można by całkiem dobrze rozwiązać, gdyby finally
konstrukcja została zaimplementowana, co pozwala programiście zapewnić, że nawet w przypadku wyjątku program będzie można przywrócić do bezpiecznego, stabilnego i szczelnego stanu, blisko punktu alokacji zasobów i potencjalnie problematycznego kodu. Jako bardzo doświadczony programista Delphi i C # używam try .. wreszcie dość obszernie blokuje mój kod, podobnie jak większość programistów w tych językach.
Biorąc pod uwagę wszystkie „dzwonki i gwizdki” zaimplementowane w C ++ 11, zdziwiłem się, widząc, że „w końcu” nadal tam nie było.
Dlaczego więc finally
konstrukcja nigdy nie została zaimplementowana w C ++? To naprawdę nie jest bardzo trudna ani zaawansowana koncepcja do zrozumienia i bardzo pomaga w pisaniu „wyjątkowego bezpiecznego kodu”.
źródło
finally
C ++ i jakie techniki obsługi wyjątków są stosowane zamiast niego?” jest ważny i dotyczy tematu tej witryny. Myślę, że istniejące odpowiedzi obejmują to dobrze. Zmieniając to w dyskusję na temat: „Czy powody, dla których projektanci C ++ nie uwzględnilifinally
wartości, są warte?” i „Czyfinally
należy dodać do C ++?” i prowadzenie dyskusji na temat komentarzy do pytania, a każda odpowiedź nie pasuje do modelu tej witryny pytań i odpowiedzi.Odpowiedzi:
Jako dodatkowy komentarz do odpowiedzi @ Nemanja (która, cytując Stroustrupa, naprawdę jest tak dobrą odpowiedzią, jak to tylko możliwe):
To naprawdę tylko kwestia zrozumienia filozofii i idiomów C ++. Weźmy przykład operacji, która otwiera połączenie z bazą danych trwałej klasy i musi upewnić się, że to połączenie zostanie zamknięte, jeśli zostanie zgłoszony wyjątek. Jest to kwestia bezpieczeństwa wyjątków i dotyczy dowolnego języka z wyjątkami (C ++, C #, Delphi ...).
W języku używającym
try
/finally
kod może wyglądać mniej więcej tak:Prosty i bezpośredni. Istnieje jednak kilka wad:
finally
blok, w przeciwnym razie wycieknie zasoby.DoRiskyOperation
jest więcej niż jedno wywołanie metody - jeśli mam trochę przetwarzania do wykonania wtry
bloku - wtedyClose
operacja może skończyć się całkiem daleko odOpen
operacji. Nie mogę napisać mojego czyszczenia tuż obok mojego nabycia.try
/finally
bloków.Podejście C ++ wygląda następująco:
To całkowicie rozwiązuje wszystkie wady tego
finally
podejścia. Ma kilka wad, ale są one stosunkowo niewielkie:ScopedDatabaseConnection
lekcję. Jest to jednak bardzo prosta implementacja - tylko 4 lub 5 linii kodu.Osobiście, biorąc pod uwagę te zalety i wady, uważam RAII za technikę o wiele bardziej preferowaną
finally
. Twój przebieg może się różnić.Wreszcie, ponieważ RAII jest tak dobrze ugruntowanym idiomem w C ++, i aby uwolnić programistów od ciężaru pisania licznych
Scoped...
klas, istnieją biblioteki takie jak ScopeGuard i Boost.ScopeExit, które ułatwiają tego rodzaju deterministyczne porządki.źródło
using
instrukcję, która automatycznie czyści każdy obiekt implementującyIDisposable
interfejs. Tak więc, chociaż można to zrobić źle, bardzo łatwo jest to naprawić.try/finally
konstrukcją, ponieważ kompilator nie ujawniatry/finally
konstrukcji, a jedynym sposobem na uzyskanie dostępu jest dostęp przez klasę idiom projektowy nie jest „zaletą”; jest to definicja inwersji abstrakcji.finally
. Jak powiedziałem, twój przebieg może się różnić.Od Dlaczego C ++ nie zapewnia konstrukcji „nareszcie”? w Bjarne Stroustrup w C ++ Style i technika FAQ :
źródło
finally
konstrukcja jest zawsze bezużyteczna na zawsze, pomimo tego , co mówi Strousup. Sam fakt, że pisanie „kodu bezpieczeństwa wyjątkowego” to wielka sprawa w C ++, jest tego dowodem. Do cholery, C # ma oba destruktory ifinally
, i oba się przyzwyczajają.Powodem, dla którego C ++ nie ma,
finally
jest to, że nie jest potrzebny w C ++.finally
służy do wykonania kodu niezależnie od tego, czy wystąpił wyjątek, czy nie, który prawie zawsze jest rodzajem kodu czyszczącego. W C ++ ten kod czyszczenia powinien znajdować się w destruktorze odpowiedniej klasy, a destruktor będzie zawsze wywoływany, podobnie jakfinally
blok. Idiom użycia destruktora do czyszczenia nazywa się RAII .W społeczności C ++ może być więcej dyskusji na temat kodu „bezpiecznego dla wyjątków”, ale jest on prawie równie ważny w innych językach, które mają wyjątki. Cały sens kodu „wyjątkowego” polega na tym, aby pomyśleć o tym, w jakim stanie kod zostanie pozostawiony, jeśli wystąpi wyjątek w dowolnej z wywoływanych funkcji / metod.
W C ++ kod „wyjątkowy” jest nieco ważniejszy, ponieważ C ++ nie ma automatycznego czyszczenia pamięci, które zajmuje się obiektami, które zostały osierocone z powodu wyjątku.
Powód, dla którego wyjątek dotyczący bezpieczeństwa jest omawiany bardziej w społeczności C ++, prawdopodobnie wynika również z faktu, że w C ++ musisz być bardziej świadomy tego, co może pójść nie tak, ponieważ w języku jest mniej domyślnych sieci bezpieczeństwa.
źródło
finally
do standardu C ++, myślę, że można bezpiecznie stwierdzić, że społeczność C ++ nie uważathe absence of finally
problemu. W większości językówfinally
brakuje konsekwentnego deterministycznego zniszczenia, jakie ma C ++. Widzę, że Delphi ma ich obu, ale nie znam wystarczająco historii, by wiedzieć, która była pierwsza.finally
”. Nigdy nie przypominam sobie żadnego zadania, które byłoby łatwiejsze, gdybym miał do niego dostęp.Inni omawiali RAII jako rozwiązanie. To idealnie dobre rozwiązanie. Ale to tak naprawdę nie dotyczy tego , dlaczego nie dodali
finally
również, ponieważ jest to bardzo pożądana rzecz. Odpowiedź na to pytanie jest bardziej fundamentalna przy projektowaniu i rozwoju C ++: zaangażowani w projektowanie C ++ zdecydowanie sprzeciwiali się wprowadzeniu funkcji projektowych, które można osiągnąć za pomocą innych funkcji bez większego zamieszania, a zwłaszcza tam, gdzie wymaga to wprowadzenia nowych słów kluczowych, które mogą spowodować niezgodność starszego kodu. Ponieważ RAII stanowi wysoce funkcjonalną alternatywę,finally
afinally
mimo to możesz samodzielnie tworzyć własne w C ++ 11, nie było potrzeby.Wszystko, co musisz zrobić, to stworzyć klasę,
Finally
która wywołuje funkcję przekazaną do konstruktora w destruktorze. Następnie możesz to zrobić:Jednak większość rodzimych programistów C ++ będzie preferować czyste obiekty RAII.
źródło
Finally atEnd([&] () { database.close(); });
też, wyobrażam sobie, że lepiej jest:{ Finally atEnd(...); try {...} catch(e) {...} }
(Podniosłem finalizator z try-block, abyMożesz użyć wzorca „pułapki” - nawet jeśli nie chcesz używać bloku try / catch.
Umieść prosty obiekt w wymaganym zakresie. W destruktorze tego obiektu umieść swoją logikę „ostateczną”. Niezależnie od tego, kiedy stos zostanie rozwinięty, zostanie wywołany destruktor obiektu, a otrzymasz cukierki.
źródło
Cóż, możesz coś w stylu roll-your-own
finally
, używając Lambdas, który uzyskałby następującą kompilację (używając przykładu bez RAII, oczywiście, nie najładniejszego kodu):Zobacz ten artykuł .
źródło
Nie jestem pewien, czy zgadzam się z twierdzeniami, że RAII jest nadzbiorem
finally
. Pięta achillesowa RAII jest prosta: wyjątki. RAII jest implementowane za pomocą destruktorów i w C ++ zawsze jest wyrzucanie z destruktora. Oznacza to, że nie możesz używać RAII, gdy potrzebujesz do wyczyszczenia kodu czyszczenia.finally
Z drugiej strony, gdyby zostały wdrożone, nie ma powodu, aby sądzić, że rzucanie zfinally
bloku byłoby niezgodne z prawem .Rozważ taką ścieżkę kodu:
Gdybyśmy mieli
finally
, moglibyśmy napisać:Ale nie mogę znaleźć równoważnego zachowania przy użyciu RAII.
Jeśli ktoś wie, jak to zrobić w C ++, jestem bardzo zainteresowany odpowiedzią. Byłbym nawet zadowolony z czegoś, na czym polegał, na przykład wymuszając, że wszystkie wyjątki odziedziczone po jednej klasie z pewnymi specjalnymi możliwościami lub czymś takim.
źródło
complex_cleanup
możesz rzucić, możesz mieć przypadek, w którym dwa nieprzechwycone wyjątki są w locie, podobnie jak w przypadku RAII / destruktorów, a C ++ nie pozwala na to. Jeśli chcesz, aby oryginalny wyjątek był widoczny,complex_cleanup
powinien zapobiegać wszelkim wyjątkom, tak jak w przypadku RAII / destruktorów. Jeśli chcesz zobaczyćcomplex_cleanup
wyjątek, myślę, że możesz użyć zagnieżdżonych bloków try / catch - chociaż jest to styczna i trudna do dopasowania w komentarzu, więc warto osobne pytanie.finally
bloku działałby wyraźnie tak samo, jak rzut wcatch
bloku WRT podczas lotu - nie sprawdzastd::terminate
. Pytanie brzmi „dlaczego niefinally
w C ++?” a wszystkie odpowiedzi mówią „nie potrzebujesz tego… RAII FTW!” Chodzi mi o to, że tak, RAII jest w porządku w prostych przypadkach, takich jak zarządzanie pamięcią, ale dopóki problem wyjątków nie zostanie rozwiązany, wymaga zbyt wiele przemyślenia / kosztów ogólnych / obaw / przeprojektowania, aby być rozwiązaniem ogólnego zastosowania.