Czy (naprawdę) piszesz wyjątkowy kod bezpieczny? [Zamknięte]

312

Obsługa wyjątków (EH) wydaje się być obecnym standardem, a przeszukując sieć, nie mogę znaleźć żadnych nowych pomysłów ani metod, które starałyby się je ulepszyć lub zastąpić (no cóż, istnieją pewne odmiany, ale nic nowego).

Chociaż większość ludzi wydaje się go ignorować lub po prostu go akceptuje, EH ma pewne poważne wady: wyjątki są niewidoczne dla kodu i tworzy wiele, wiele możliwych punktów wyjścia. Joel on software napisał o tym artykuł . Porównanie gotoidealnie pasuje, sprawiło, że pomyślałem o EH.

Staram się unikać EH i po prostu używam wartości zwracanych, wywołań zwrotnych lub czegokolwiek, co pasuje do celu. Ale kiedy musisz napisać niezawodny kod, po prostu nie możesz teraz zignorować EH : zaczyna się od newznaku, który może zgłosić wyjątek, zamiast zwracać 0 (jak za dawnych czasów). Powoduje to, że każda linia kodu C ++ jest podatna na wyjątek. A potem więcej miejsc w podstawowym kodzie C ++ generuje wyjątki ... std lib robi to i tak dalej.

To tak, jakby chodzić po chwiejnych terenach . Teraz jesteśmy zmuszeni dbać o wyjątki!

Ale to trudne, to naprawdę trudne. Musisz nauczyć się pisać wyjątkowy kod bezpieczny, a nawet jeśli masz z tym pewne doświadczenie, nadal będzie konieczne podwójne sprawdzenie dowolnego wiersza kodu, aby był bezpieczny! Lub zaczynasz wszędzie umieszczać bloki try / catch, co zaśmieca kod, aż osiągnie stan nieczytelności.

EH zastąpił stare czyste, deterministyczne podejście (zwracane wartości ..), które miało tylko kilka, ale zrozumiałych i łatwych do rozwiązania wad, podejściem, które tworzy wiele możliwych punktów wyjścia w kodzie, a jeśli zaczniesz pisać kod wychwytujący wyjątki (to, co w pewnym momencie są do tego zmuszone), a nawet tworzy wiele ścieżek w kodzie (kod w blokach catch, pomyśl o programie serwera, w którym potrzebujesz funkcji logowania innych niż std :: cerr ..). EH ma zalety, ale nie o to chodzi.

Moje aktualne pytania:

  • Czy naprawdę piszesz kod bezpieczny wyjątek?
  • Czy na pewno Twój ostatni kod „gotowy do produkcji” jest wyjątkowo bezpieczny?
  • Czy możesz być nawet pewien, że tak jest?
  • Czy znasz i / lub faktycznie korzystasz z alternatyw, które działają?
Frunsi
źródło
44
„EH zastąpił stare czyste podejście deterministyczne (zwracane wartości…)” Co? Wyjątki są tak samo deterministyczne jak kody powrotu. Nie ma w nich nic losowego.
rlbond
2
EH jest standardem od dłuższego czasu (od Ady nawet!)
12
Czy „EH” to ustalony skrót do obsługi wyjątków? Nigdy wcześniej tego nie widziałem.
Thomas Padron-McCarthy,
3
Prawa Newtona obowiązują dziś w większości przypadków. Znaczący jest fakt, że od stuleci przewidywali bardzo szeroki zakres zjawisk.
David Thornley,
4
@frunsi: Heh, przepraszam za zamieszanie! Myślę jednak, że dziwny i niewyjaśniony akronim lub skrót zniechęci ludzi bardziej niż nie.
Thomas Padron-McCarthy,

Odpowiedzi:

520

Twoje pytanie zawiera stwierdzenie, że „Pisanie kodu bezpiecznego dla wyjątków jest bardzo trudne”. Najpierw odpowiem na twoje pytania, a następnie odpowiem na ukryte pytanie za nimi.

Odpowiadanie na pytania

Czy naprawdę piszesz kod bezpieczny wyjątek?

Oczywiście, że tak.

To jest powód, dla którego Java straciła wiele na atrakcyjności dla mnie jako programisty C ++ (brak semantyki RAII), ale dygresję: To jest pytanie C ++.

Jest to w rzeczywistości konieczne, gdy trzeba pracować z kodem STL lub Boost. Na przykład, nici C ++ ( boost::threadi std::thread) będzie wyjątek, aby zamknąć bezpiecznie.

Czy na pewno Twój ostatni kod „gotowy do produkcji” jest wyjątkowo bezpieczny?

Czy możesz być nawet pewien, że tak jest?

Pisanie kodu bezpiecznego dla wyjątków jest jak pisanie kodu wolnego od błędów.

Nie możesz być w 100% pewien, że Twój kod jest wyjątkowo bezpieczny. Ale potem dążysz do tego, używając dobrze znanych wzorów i unikając dobrze znanych anty-wzorów.

Czy znasz i / lub faktycznie korzystasz z alternatyw, które działają?

W C ++ nie ma realnych alternatyw (tzn. Musisz wrócić do C i unikać bibliotek C ++, a także zewnętrznych niespodzianek, takich jak Windows SEH).

Pisanie kodu bezpiecznego wyjątku

Do bezpiecznego kodu zapisu wyjątku, trzeba wiedzieć, pierwszy , jaki poziom bezpieczeństwa wyjątku każda instrukcja piszesz jest.

Na przykład a newmoże zgłosić wyjątek, ale przypisanie wbudowanego (np. Int lub wskaźnika) nie zakończy się niepowodzeniem. Zamiana nigdy się nie zawiedzie (nigdy nie pisz zamiany rzucania), std::list::push_backmoże rzucić ...

Gwarancja wyjątku

Pierwszą rzeczą do zrozumienia jest to, że musisz być w stanie ocenić gwarancję wyjątku oferowaną przez wszystkie swoje funkcje:

  1. none : Twój kod nigdy nie powinien tego oferować. Ten kod przecieka wszystko i ulega awarii przy pierwszym zgłoszonym wyjątku.
  2. podstawowa : jest to gwarancja, którą musisz przynajmniej zaoferować, to znaczy, jeśli zostanie zgłoszony wyjątek, nie wyciekną żadne zasoby, a wszystkie obiekty będą nadal w całości
  3. strong : Przetwarzanie zakończy się powodzeniem lub wyrzuci wyjątek, ale jeśli zgłosi wyjątek, dane będą w tym samym stanie, jakby przetwarzanie w ogóle się nie rozpoczęło (daje to możliwość transakcyjną C ++)
  4. nothrow / nofail : Przetwarzanie zakończy się powodzeniem.

Przykład kodu

Poniższy kod wydaje się być poprawnym C ++, ale tak naprawdę oferuje gwarancję „brak”, a zatem nie jest poprawny:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

Cały mój kod piszę z myślą o tego rodzaju analizach.

Najniższa oferowana gwarancja jest podstawowa, ale wtedy kolejność każdej instrukcji powoduje, że cała funkcja jest „brak”, ponieważ jeśli 3. wyrzuci, x wycieknie.

Pierwszą rzeczą do zrobienia byłoby uczynienie funkcji „podstawową”, czyli umieszczenie x w inteligentnym wskaźniku, dopóki nie będzie bezpiecznie własnością listy:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 4.  basic : can throw
}

Teraz nasz kod oferuje „podstawową” gwarancję. Nic nie wycieknie, a wszystkie obiekty będą w prawidłowym stanie. Ale moglibyśmy zaoferować więcej, czyli silną gwarancję. To może być kosztowne i dlatego nie cały kod C ++ jest silny. Spróbujmy:

void doSomething(T & t)
{
   // we create "x"
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 3. basic : can throw

   // we copy the original container to avoid changing it
   T t2(t) ;                        // 4. strong : can throw with T copy-constructor

   // we put "x" in the copied container
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   if(std::numeric_limits<int>::max() > t2.integer)  // 7.   nothrow/nofail
      t2.integer += 1 ;                              // 7'.  nothrow/nofail

   // we swap both containers
   t.swap(t2) ;                     // 8. nothrow/nofail
}

Zmieniliśmy kolejność operacji, najpierw tworząc i ustawiając Xodpowiednią wartość. Jeśli jakakolwiek operacja zakończy się niepowodzeniem, tnie jest modyfikowana, więc operację od 1 do 3 można uznać za „silną”: jeśli coś rzuci, tnie zostanie zmodyfikowane i Xnie wycieknie, ponieważ jest własnością inteligentnego wskaźnika.

Następnie tworzymy kopię t2z t, a działanie tej kopii z eksploatacji 4 do 7. Jeśli coś rzuca, t2jest modyfikowana, ale potem, twciąż jest oryginalny. Nadal oferujemy silną gwarancję.

Następnie zamieniamy ti t2. Operacje wymiany nie powinny być wykonywane w C ++, więc miejmy nadzieję, że zamiana, dla której napisałeś, Tjest nothrow (jeśli nie jest, przepisz, żeby nie była).

Tak więc, jeśli dojdziemy do końca funkcji, wszystko się powiedzie (nie ma potrzeby zwracania typu) i tma swoją wyjątek. Jeśli się nie powiedzie, to tnadal ma swoją oryginalną wartość.

Teraz, oferowanie silnej gwarancji może być dość kosztowne, więc nie staraj się oferować silnej gwarancji dla całego swojego kodu, ale jeśli możesz to zrobić bez kosztów (a wbudowane C ++ i inna optymalizacja mogą sprawić, że cały kod będzie droższy) , a następnie zrób to. Użytkownik funkcji będzie ci za to wdzięczny.

Wniosek

Pisanie kodu bezpiecznego dla wyjątków wymaga pewnego nawyku. Musisz ocenić gwarancję oferowaną przez każdą instrukcję, której użyjesz, a następnie musisz ocenić gwarancję oferowaną przez listę instrukcji.

Oczywiście, kompilator C ++ nie utworzy kopii zapasowej gwarancji (w moim kodzie oferuję gwarancję jako tag @warning doxygen), co jest trochę smutne, ale nie powinno powstrzymywać cię przed próbą napisania kodu bezpiecznego dla wyjątków.

Normalna awaria vs. błąd

W jaki sposób programista może zagwarantować, że funkcja bezawaryjna zawsze się powiedzie? W końcu funkcja może mieć błąd.

To prawda. Gwarancje wyjątków powinny być oferowane przez kod wolny od błędów. Ale w każdym języku wywołanie funkcji zakłada, że ​​funkcja jest wolna od błędów. Żaden rozsądny kod nie chroni się przed możliwością wystąpienia błędu. Napisz kod najlepiej, jak potrafisz, a następnie zaoferuj gwarancję, zakładając, że jest on wolny od błędów. A jeśli występuje błąd, popraw go.

Wyjątek stanowią wyjątkowe błędy przetwarzania, a nie błędy w kodzie.

Ostatnie słowa

Teraz pytanie brzmi: „Czy warto?”.

Oczywiście, że jest. Posiadanie funkcji „nothrow / no-fail” wiedząc, że funkcja się nie zawiedzie, jest wielkim dobrodziejstwem. To samo można powiedzieć o „silnej” funkcji, która umożliwia pisanie kodu z semantyką transakcyjną, taką jak bazy danych, z funkcjami zatwierdzania / wycofywania zmian, przy czym zatwierdzanie jest normalnym wykonaniem kodu, z wyjątkami będącymi wycofywaniem.

Zatem „podstawowa” jest najmniejszą gwarancją, którą powinieneś zaoferować. C ++ jest tam bardzo silnym językiem, którego zakresy pozwalają uniknąć wycieków zasobów (coś, co dla śmieciarza byłoby trudne do zaoferowania dla bazy danych, połączenia lub uchwytów plików).

Tak więc, o ile ja to widzę, to jest warto.

Edytuj 2010-01-29: O zamianie bez rzucania

nobar skomentował, co moim zdaniem, jest dość trafny, ponieważ jest częścią „jak napisać kod bezpieczny wyjątku”:

  • [ja] Zamiana nigdy się nie zawiedzie (nawet nie pisz zamiany rzucania)
  • [nobar] To dobra rekomendacja dla niestandardowych swap()funkcji. Należy jednak zauważyć, że std::swap()może się nie powieść na podstawie operacji, których używa wewnętrznie

domyślnie std::swapbędą tworzyć kopie i przypisania, które dla niektórych obiektów mogą rzucać. Tak więc domyślna zamiana mogłaby rzucać, używana dla twoich klas, a nawet dla klas STL. Jeśli chodzi o standard C ++, operacja zamiany dla vector, dequei listnie będzie rzucać, podczas gdy może to zrobić, mapjeśli funktor porównawczy może rzucić na budowę kopii (patrz The C ++ Programming Language, wydanie specjalne, dodatek E, E.4.3 . Zamień ).

Patrząc na implementację wymiany wektora w Visual C ++ 2008, zamiana wektora nie będzie rzucać, jeśli dwa wektory mają ten sam alokator (tj. Normalny przypadek), ale wykona kopie, jeśli mają różne alokatory. Tak więc zakładam, że może to rzucić w tym ostatnim przypadku.

Tak więc oryginalny tekst nadal obowiązuje: nigdy nie pisz zamiany rzucania, ale komentarz nobara musi zostać zapamiętany: upewnij się, że wymieniane obiekty mają zamianę rzucania.

Edytuj 2011-11-06: Ciekawy artykuł

Dave Abrahams , który udzielił nam podstawowych / silnych / niezapowiedzianych gwarancji , opisał w swoim artykule swoje doświadczenie związane z zapewnieniem bezpieczeństwa wyjątku STL:

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

Spójrz na 7 punkt (automatyczne testowanie w celu zapewnienia bezpieczeństwa wyjątkowego), gdzie polega on na automatycznych testach jednostkowych, aby upewnić się, że każdy przypadek jest testowany. Myślę, że ta część jest doskonałą odpowiedzią na pytanie autora „ Czy możesz być pewien, że tak jest? ”.

Edytuj 2013-05-31: Komentarz od dionadaru

t.integer += 1;nie ma gwarancji, że przelew się nie wydarzy, NIE jest wyjątkowo bezpieczny, aw rzeczywistości może technicznie wywołać UB! (Przepełnienie ze znakiem to UB: C ++ 11 5/4 „Jeśli podczas oceny wyrażenia wynik nie jest zdefiniowany matematycznie lub nie mieści się w zakresie reprezentatywnych wartości dla jego typu, zachowanie jest niezdefiniowane.”) Należy zauważyć, że bez znaku liczby całkowite nie przepełniają się, lecz wykonują swoje obliczenia w klasie równoważności modulo 2 ^ # bitów.

Dionadar odnosi się do następującej linii, która rzeczywiście ma nieokreślone zachowanie.

   t.integer += 1 ;                 // 1. nothrow/nofail

Rozwiązaniem tutaj jest sprawdzenie, czy liczba całkowita ma już maksymalną wartość (używanie std::numeric_limits<T>::max()) przed dodaniem.

Mój błąd przejdzie do sekcji „Normalna awaria vs. błąd”, czyli błąd. Nie unieważnia to rozumowania i nie oznacza, że ​​kod bezpieczny dla wyjątków jest bezużyteczny, ponieważ niemożliwy do osiągnięcia. Nie można zabezpieczyć się przed wyłączeniem komputera, błędami kompilatora, a nawet błędami lub innymi błędami. Nie możesz osiągnąć doskonałości, ale możesz spróbować zbliżyć się jak najbliżej.

Poprawiłem kod, mając na uwadze komentarz Dionadara.

paercebal
źródło
6
Wielkie dzięki! Wciąż liczyłem na coś innego. Ale akceptuję twoją odpowiedź za trzymanie się C ++ i szczegółowe wyjaśnienie. Odrzuciłeś nawet moje „To powoduje, że każda linia kodu C ++ jest podatna na wyjątek”. Ta analiza linia po linii ma jednak sens ... Muszę o tym pomyśleć.
Frunsi,
8
„Zamiana nigdy nie zawiedzie”. Jest to dobra rekomendacja dla niestandardowych funkcji swap (). Należy jednak zauważyć, że std :: swap () może zawieść w zależności od operacji, których używa wewnętrznie.
nobar
7
@Mehrdad:: "finally" does exactly what you were trying to achieveOczywiście, że tak. Ponieważ tworzenie kodu łamliwego jest łatwe, w Javie 7 ostatecznie wprowadzono finallypojęcie „spróbuj z zasobem” (10 lat po C # i 3 dekady po niszczarkach C ++). Właśnie tę kruchość krytykuję. Jeśli chodzi o : W tym przypadku przemysł nie zgadza się z twoim gustem, ponieważ języki Garbage Collected mają tendencję do dodawania instrukcji inspirowanych RAII (C # i Java ). usingJust because [finally] doesn't match your taste (RAII doesn't match mine, [...]) doesn't mean it's "failing"usingtry
paercebal,
5
@Mehrdad:: RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimesNie, nie ma. Możesz użyć inteligentnych wskaźników lub klas narzędzi do „ochrony” zasobów.
paercebal
5
@Mehrdad: „zmusza cię do stworzenia opakowania na coś, do czego może nie być potrzebne opakowanie” - podczas kodowania w stylu RAII nie będziesz potrzebować żadnego opakowania: zasobem jest obiekt, a żywotność obiektów to żywotność zasobów. Jednak pochodzę ze świata C ++, obecnie mam problemy z projektem Java. W porównaniu do RAII w C ++, „ręczne zarządzanie zasobami” w Javie JEST MESS! Moja opinia, ale Java dawno temu wymieniała „automatyczne pamięci mgmt” na „automatyczne zasoby mgmt”.
Frunsi
32

Pisanie kodu bezpiecznego dla wyjątków w C ++ nie polega na użyciu wielu bloków try {} catch {}. Chodzi o dokumentowanie, jakie rodzaje gwarancji zapewnia Twój kod.

Polecam przeczytanie serii Guru tygodnia Tygodnia Herb Suttera , w szczególności części 59, 60 i 61.

Podsumowując, istnieją trzy poziomy bezpieczeństwa wyjątkowego, które możesz zapewnić:

  • Podstawowy: gdy kod zgłasza wyjątek, kod nie przecieka zasobów, a obiekty pozostają zniszczalne.
  • Silny: gdy kod zgłasza wyjątek, pozostawia stan aplikacji bez zmian.
  • Bez rzucania: Twój kod nigdy nie zgłasza wyjątków.

Osobiście odkryłem te artykuły dość późno, więc większość mojego kodu C ++ zdecydowanie nie jest bezpieczna dla wyjątków.

Joh
źródło
1
Jego książka „Wyjątkowa C ++” jest również dobrą lekturą. Ale wciąż próbuję zakwestionować koncepcję EH ...
Frunsi,
8
+1 OP łączy obsługę wyjątków (łapanie ich) z bezpieczeństwem wyjątków (zwykle więcej o RAII)
jk.
Podejrzewam, że bardzo mało produkcyjnego kodu C ++ jest wyjątkowo bezpieczne.
Raedwald
18

Niektórzy z nas korzystają z wyjątku od ponad 20 lat. PL / I je ma na przykład. Przesłanka, że ​​są one nową i niebezpieczną technologią, wydaje mi się wątpliwa.

bmargulies
źródło
Nie zrozumcie mnie źle, przesłuchuję EH (lub próbuję). A zwłaszcza C ++ EH. I szukam alternatyw. Może muszę to zaakceptować (i zrobię to, jeśli to jedyny sposób), ale myślę, że mogą być lepsze alternatywy. Nie chodzi o to, że myślę, że koncepcja jest nowa, ale tak, myślę, że może być bardziej niebezpieczna niż jawna obsługa błędów za pomocą kodów powrotu ...
Frunsi
1
Jeśli ci się nie podoba, nie używaj go. Umieść bloki próbne wokół rzeczy, które musisz wywołać, które mogą rzucać, i wymyśl nowy kod błędu-olde i cierpieć z powodu problemów, które w niektórych przypadkach są tolerowane.
bmargulies,
Ok, idealnie, użyję EH i kodów błędów i będę z tym żyć. Jestem głupkiem, sam powinienem był znaleźć to rozwiązanie! ;)
Frunsi,
17

Po pierwsze (jak stwierdził Neil), SEH to Structured Exception Handling Microsoftu. Jest podobny do przetwarzania wyjątków w C ++, ale nie identyczny. W rzeczywistości musisz włączyć obsługę wyjątków C ++, jeśli chcesz w Visual Studio - domyślne zachowanie nie gwarantuje zniszczenia obiektów lokalnych we wszystkich przypadkach! W obu przypadkach obsługa wyjątków nie jest trudniejsza , po prostu inna .

Teraz twoje aktualne pytania.

Czy naprawdę piszesz kod bezpieczny wyjątek?

Tak. We wszystkich przypadkach staram się o wyjątkowy kod bezpieczny. Ewangelizuję za pomocą technik RAII w celu uzyskania dostępu do zasobów w określonym zakresie (np. boost::shared_ptrDo pamięci, boost::lock_guarddo blokowania). Ogólnie rzecz biorąc, konsekwentne stosowanie technik RAII i technik ochrony zakresu znacznie ułatwi pisanie kodu bezpiecznego wyjątku. Sztuką jest dowiedzieć się, co istnieje i jak go zastosować.

Czy na pewno Twój ostatni kod „gotowy do produkcji” jest wyjątkowo bezpieczny?

Nie. Jest tak bezpieczny, jak to jest. Mogę powiedzieć, że nie widziałem błędu procesu z powodu wyjątku przez kilka lat działalności 24/7. Nie oczekuję idealnego kodu, tylko dobrze napisany kod. Oprócz zapewnienia wyjątkowego bezpieczeństwa, powyższe techniki gwarantują poprawność w sposób, który jest prawie niemożliwy do osiągnięcia przy pomocy try/ catchbloków. Jeśli łapiesz wszystko w swoim najwyższym zakresie kontrolnym (wątek, proces itp.), Możesz być pewien, że będziesz nadal działał w obliczu wyjątków (przez większość czasu ). Te same techniki pomogą ci nadal działać poprawnie w obliczu wyjątków bez try/ catchbloków wszędzie .

Czy możesz być nawet pewien, że tak jest?

Tak. Możesz być pewien dokładnego audytu kodu, ale tak naprawdę nikt tego nie robi? Regularne przeglądy kodu i ostrożni programiści bardzo się do tego przyczyniają.

Czy znasz i / lub faktycznie korzystasz z alternatyw, które działają?

Przez lata próbowałem kilku odmian, takich jak kodowanie stanów w wyższych bitach (ala HRESULTs ) lub ten okropny setjmp() ... longjmp()hack. Oba z nich rozpadają się w praktyce, choć na zupełnie inne sposoby.


W końcu, jeśli nauczysz się stosować kilka technik i dokładnie zastanawiasz się, gdzie możesz rzeczywiście zrobić coś w odpowiedzi na wyjątek, otrzymasz bardzo czytelny kod, który jest wyjątkowo bezpieczny. Możesz to podsumować, przestrzegając następujących zasad:

  • Chcesz tylko zobaczyć try/ catchkiedy możesz coś zrobić z konkretnym wyjątkiem
  • Prawie nigdy nie chcesz zobaczyć surowego kodu newlub deletekodu
  • Unikaj std::sprintf, snprintfi ogólnie tablic - używaj std::ostringstreamdo formatowania i zamieniaj tablice na std::vectoristd::string
  • W razie wątpliwości sprawdź funkcjonalność w trybie Boost lub STL przed uruchomieniem własnego

Mogę tylko zalecić, abyś nauczył się prawidłowo używać wyjątków i zapomniał o kodach wyników, jeśli planujesz pisać w C ++. Jeśli chcesz uniknąć wyjątków, możesz rozważyć pisanie w innym języku, który ich nie ma lub zapewnia im bezpieczeństwo . Jeśli chcesz naprawdę nauczyć się, jak w pełni korzystać z C ++, przeczytaj kilka książek z Herb Sutter , Nicolai Josuttis i Scott Meyers .

D.Shawley
źródło
„domyślne zachowanie nie gwarantuje, że lokalne obiekty zostaną zniszczone we wszystkich przypadkach” Mówisz, że domyślne ustawienie kompilatora Visual Studio C ++ generuje kod, który jest nieprawidłowy w obliczu wyjątków. Czy to naprawdę tak jest?
Raedwald
„Prawie nigdy nie chcesz widzieć surowego kodu newlub deletekodu”: domyślnie rozumiem, że masz na myśli konstruktor lub destruktor.
Raedwald
@ Raedwald - re: VC ++: wersja VC ++ VS2005 nie zniszczy obiektów lokalnych, gdy zostanie zgłoszony wyjątek SEH. Przeczytaj „Włącz obsługę wyjątków C ++” . W VS2005 wyjątki SEH domyślnie nie wywołują niszczycieli obiektów C ++. Jeśli wywołasz funkcje Win32 lub cokolwiek zdefiniowane w bibliotece DLL interfejsu C, musisz się tym martwić, ponieważ mogą (i czasami będą) rzucać wyjątek SEH na twój sposób.
D.Shawley,
@ Raedwald: re: raw : zasadniczo deletenigdy nie należy go używać poza implementacją tr1::shared_ptri tym podobnych. newmoże być używany pod warunkiem, że jego użycie jest podobne tr1::shared_ptr<X> ptr(new X(arg, arg));. Ważną częścią jest to, że wynik newprzechodzi bezpośrednio do zarządzanego wskaźnika. Strona na boost::shared_ptrDobrych Praktyk opisuje najlepsze.
D.Shawley,
Dlaczego odwołujesz się do std :: sprintf (i in.) W swoim zestawie reguł dotyczących bezpieczeństwa wyjątkowego? Nie dokumentują, że zgłaszają
mabraham
10

Nie można napisać kodu bezpiecznego dla wyjątków przy założeniu, że „dowolna linia może wyrzucić”. Projektowanie kodu bezpiecznego dla wyjątków opiera się krytycznie na pewnych umowach / gwarancjach, których należy się spodziewać, przestrzegać, przestrzegać i wdrażać w kodzie. Konieczne jest posiadanie kodu, który gwarantuje, że nigdy nie wyrzuci. Istnieją inne rodzaje gwarancji wyjątkowych.

Innymi słowy, tworzenie kodu bezpiecznego dla wyjątków jest w dużej mierze kwestią projektu programu, a nie tylko zwykłego kodowania .

Mrówka
źródło
8
  • Czy naprawdę piszesz kod bezpieczny wyjątek?

Z pewnością zamierzam.

  • Czy na pewno Twój ostatni kod „gotowy do produkcji” jest wyjątkowo bezpieczny?

Jestem pewien, że moje serwery 24/7 zbudowane przy użyciu wyjątków działają 24/7 i nie przeciekają pamięci.

  • Czy możesz być nawet pewien, że tak jest?

Bardzo trudno jest upewnić się, że dowolny kod jest poprawny. Zazwyczaj można przejść tylko według wyników

  • Czy znasz i / lub faktycznie korzystasz z alternatyw, które działają?

Nie. Używanie wyjątków jest czystsze i łatwiejsze niż jakakolwiek alternatywa, z której korzystałem przez ostatnie 30 lat w programowaniu.

zaraz
źródło
30
Ta odpowiedź nie ma wartości.
Matt Joiner,
6
@MattJoiner To pytanie nie ma wartości.
Miles Rout
5

Pomijając pomieszanie wyjątków SEH i C ++, musisz mieć świadomość, że wyjątki mogą być zgłaszane w dowolnym momencie, i napisz o tym swój kod. Potrzeba bezpieczeństwa wyjątkowego jest w dużej mierze tym, co napędza użycie RAII, inteligentnych wskaźników i innych nowoczesnych technik C ++.

Jeśli postępujesz zgodnie z ustalonymi wzorcami, pisanie kodu bezpiecznego dla wyjątków nie jest szczególnie trudne, a w rzeczywistości jest łatwiejsze niż pisanie kodu, który poprawnie obsługuje zwroty błędów we wszystkich przypadkach.

Mark Bessey
źródło
3

EH jest ogólnie dobre. Ale implementacja C ++ nie jest zbyt przyjazna, ponieważ naprawdę trudno jest stwierdzić, jak dobry jest twój zasięg wychwytywania wyjątków. Java na przykład ułatwia to, kompilator może zawieść, jeśli nie obsłużysz możliwych wyjątków.

Mr. Boy
źródło
2
Jest znacznie lepiej z rozsądnym użyciem noexcept.
einpoklum
2

Naprawdę lubię pracować z Eclipse i Javą (nowość w Javie), ponieważ generuje błędy w edytorze, jeśli brakuje Ci programu obsługi EH. To sprawia, że ​​dużo trudniej jest zapomnieć o obsłudze wyjątku ...

Dodatkowo dzięki narzędziom IDE automatycznie dodaje blok try / catch lub inny blok catch.

Crowe T. Robot
źródło
5
Na tym polega różnica między sprawdzonymi (Java) i niesprawdzonymi (c ++) wyjątkami (Java też ma kilka takich wyjątków). Zaletą sprawdzonych wyjątków jest to, co napisałeś, ale tam ma swoje wady. Google podchodzi do różnic i różnych problemów, jakie mają.
David Rodríguez - dribeas
2

Niektórzy z nas preferują języki takie jak Java, które zmuszają nas do deklarowania wszystkich wyjątków zgłaszanych przez metody, zamiast uczynić je niewidocznymi, jak w C ++ i C #.

Prawidłowo wykonane wyjątki przewyższają kody powrotu błędów, jeśli z innego powodu nie trzeba ręcznie propagować awarii w łańcuchu połączeń.

To powiedziawszy, programowanie biblioteki API niskiego poziomu powinno prawdopodobnie unikać obsługi wyjątków i trzymać się kodów powrotu błędów.

Z mojego doświadczenia wynika, że ​​trudno jest napisać czysty kod obsługi wyjątków w C ++. W końcu new(nothrow)dużo używam .

David R. Tribble
źródło
4
I unikasz też większości standardowych bibliotek? Używanie new(std::nothrow)nie wystarczy. Nawiasem mówiąc, łatwiej jest napisać kod bezpieczny dla wyjątków w C ++ niż w Javie: en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
avakar
3
Obsługa sprawdzonego wyjątku Java jest bardzo przesadzona. W rzeczywistości języki inne niż Java NIE są uważane za sukces. To dlatego instrukcja „throw” w C ++ jest obecnie uważana za przestarzałą, a C # nigdy poważnie nie rozważał ich implementacji (był to wybór projektowy). W przypadku Javy następujący dokument może być pouczający
paercebal,
2
Z mojego doświadczenia wynika, że ​​pisanie kodu bezpiecznego dla wyjątków w C ++ nie jest takie trudne i że ogólnie prowadzi do czystszego kodu. Oczywiście musisz się tego nauczyć.
David Thornley,
2

Dokładam wszelkich starań, aby napisać kod bezpieczny dla wyjątków, tak.

Oznacza to, że zwracam uwagę na to, które linie mogą rzucać. Nie każdy może i należy mieć to na uwadze. Kluczem do sukcesu jest przemyślenie i zaprojektowanie kodu tak, aby spełniał gwarancje wyjątków określone w standardzie.

Czy można napisać tę operację, aby zapewnić silną gwarancję wyjątku? Czy muszę zadowolić się podstawowym? Które linie mogą zgłaszać wyjątki i jak mogę się upewnić, że jeśli to zrobią, nie uszkodzą obiektu?

jalf
źródło
2
  • Czy naprawdę piszesz kod bezpieczny wyjątek? [Nie ma takiej rzeczy. Wyjątki stanowią papierową tarczę przed błędami, chyba że masz środowisko zarządzane. Dotyczy to pierwszych trzech pytań.]

  • Czy znasz i / lub faktycznie korzystasz z alternatyw, które działają? [Alternatywa do czego? Problem polega na tym, że ludzie nie oddzielają rzeczywistych błędów od normalnego działania programu. Jeśli jest to normalne działanie programu (tzn. Nie znaleziono pliku), to tak naprawdę nie jest to obsługa błędów. Jeśli jest to rzeczywisty błąd, nie ma sposobu, aby go „obsłużyć” lub nie jest to rzeczywisty błąd. Twoim celem jest dowiedzieć się, co poszło nie tak, albo zatrzymać arkusz kalkulacyjny i zapisać błąd, ponownie uruchomić sterownik do tostera lub po prostu modlić się, aby myśliwiec nadal mógł latać, nawet jeśli jego oprogramowanie jest wadliwe i ma nadzieję na najlepsze.]

Charles Eli Cheese
źródło
0

Dużo (powiedziałbym nawet większość) ludzi.

Najważniejsze w wyjątkach jest to, że jeśli nie napiszesz żadnego kodu obsługi - wynik jest całkowicie bezpieczny i dobrze się zachowuje. Zbyt chętny do paniki, ale bezpieczny.

Musisz aktywnie popełniać błędy w modułach obsługi, aby uzyskać coś niebezpiecznego, a tylko catch (...) {} będzie porównywany do ignorowania kodu błędu.

ima
źródło
4
Nie prawda. Bardzo łatwo jest napisać kod, który nie jest bezpieczny dla wyjątków. Na przykład: f = new foo(); f->doSomething(); delete f;jeśli metoda doSomething zgłasza wyjątek, oznacza to przeciek pamięci.
Kristopher Johnson,
1
Nie ma znaczenia, kiedy program się kończy, prawda? Aby kontynuować wykonywanie, musisz aktywnie połykać wyjątki. Cóż, istnieją pewne szczególne przypadki, w których zakończenie bez czyszczenia jest nadal niedopuszczalne, ale takie sytuacje wymagają szczególnej uwagi w każdym języku i stylu programowania.
ima
Po prostu nie można ignorować wyjątków (i nie pisać kodu obsługi), ani w C ++, ani w kodzie zarządzanym. Będzie to niebezpieczne i nie będzie dobrze się zachowywać. Z wyjątkiem może jakiegoś kodu zabawki.
Frunsi,
1
Jeśli zignorujesz wyjątki w kodzie aplikacji, program może nadal NIE zachowywać się dobrze, gdy w grę wchodzą zewnętrzne zasoby. To prawda, że ​​system operacyjny dba o zamykanie uchwytów plików, blokad, gniazd i tak dalej. Ale nie wszystko jest obsługiwane, np. Może pozostawić niepotrzebne pliki lub uszkodzić pliki podczas pisania do nich i tak dalej. Jeśli zignorujesz wyjątki, masz problem z Javą, w C ++ możesz pracować z RAII (ale kiedy używasz techniki RAII, najprawdopodobniej używasz ich, ponieważ zależy Ci na wyjątkach) ...
Frunsi
3
Proszę, nie przekręcaj moich słów. Napisałem „jeśli nie piszesz żadnego kodu obsługi” - i miałem na myśli dokładnie to. Aby zignorować wyjątki, musisz napisać kod.
ima