2010-12-29 12 views
7

Od czasu do czasu zauważam pewien wzór kodowania, który miałem od lat i denerwuje mnie to. Nie mam konkretnego problemu, ale nie pamiętam też wystarczająco, dlaczego przyjąłem ten wzorzec, a jakiś jego aspekt wydaje się pasować do jakiegoś wzorca. Niedawno zdarzyło mi się WRT, jak niektóre z mojego kodu używają wyjątków.Czy używam klauzuli catch C++, rodziny klas wyjątków i destrukcji?

Niepokojąca sprawa dotyczy przypadków, w których przechwyciłem wyjątek "przez odniesienie", traktując go w podobny sposób, w jaki sposób potraktowałem parametr funkcji. Jednym z powodów jest to, że mogę mieć hierarchię dziedziczenia klas wyjątków i określić bardziej ogólny lub bardziej precyzyjny typ catch, w zależności od aplikacji. Na przykład, mogę określić ...

class widget_error {}; 
class widget_error_all_wibbly : public widget_error {}; 
class widget_error_all_wobbly : public widget_error {}; 

void wibbly_widget() 
{ 
    throw widget_error_all_wibbly(); 
} 

void wobbly_widget() 
{ 
    throw widget_error_all_wobbly(); 
} 

void call_unknown_widget (void (*p_widget)()) 
{ 
    try 
    { 
    p_widget(); 
    } 
    catch (const widget_error &p_exception) 
    { 
    // Catches either widget_error_all_wibbly or 
    // widget_error_all_wobbly, or a plain widget_error if that 
    // is ever thrown by anything. 
    } 
} 

to teraz mnie niepokojące, ponieważ zauważyłem, że instancja klasy jest zbudowany (jako część rzutu) wewnątrz funkcji, ale jest wymieniony (przez p_Exception catch-clause "parameter") po wyjściu z tej funkcji. Zwykle jest to wzorzec zapobiegający - referencja lub wskaźnik do zmiennej lokalnej lub tymczasowej utworzonej w ramach funkcji, ale zanikający, gdy funkcja jest zakończona, jest zwykle zwisającym odnośnikiem/wskaźnikiem, ponieważ zmienna lokalna/tymczasowa jest niszczona, a pamięć zwalniana. po wyjściu funkcji.

Niektóre szybkie testy sugerują, że powyższy rzut jest prawdopodobnie poprawny - instancja skonstruowana w klauzuli throw nie zostanie zniszczona, gdy funkcja zostanie zakończona, ale zostanie zniszczona po zakończeniu catch-clause, która ją obsługuje - chyba że blok catch rzuci ponownie wyjątek, w którym to przypadku następny blok catch wykonuje to zadanie.

Moja pozostała nerwowość polega na tym, że test przeprowadzony w jednym lub dwóch kompilatorach nie jest dowodem na to, co mówi standard, a ponieważ moje doświadczenie mówi, że to, co uważam za zdrowy rozsądek, często różni się od tego, co gwarantuje język.

Czy ten wzór obsługi wyjątków (łapanie ich przy użyciu typu odniesienia) jest bezpieczny? Czy powinienem robić coś innego, takie jak ...

  • Catching (i jawnie usuwanie) wskaźniki do wystąpień sterty przydzielone zamiast odniesienia do czegoś, co wygląda (gdy rzucony) bardzo podobny tymczasowy?
  • Korzystanie z klasy inteligentnego wskaźnika?
  • Korzystanie z klauzul catch catch-by-value i akceptacja, że ​​nie mogę przechwycić żadnej klasy wyjątków z hierarchii z jedną klauzulą ​​catch?
  • Coś, o czym nie pomyślałem?
+2

Również [używaj wirtualnego dziedziczenia] (http://www.boost.org/community/error_handling.html). – ybungalobill

+1

@ybungalobill - Myślałem, że to żart, a potem przeczytałem link. Interesujący punkt. – Steve314

Odpowiedz

9

To jest w porządku. Właściwie dobrze jest wychwycić wyjątki przez stałe odniesienie (i złe do złapania wskaźników). Łapanie według wartości tworzy niepotrzebną kopię. Kompilator jest wystarczająco inteligentny, aby poprawnie obsłużyć wyjątek (i jego zniszczenie) - po prostu nie używaj wyjątku poza blokiem catch ;-)

W rzeczywistości, co często robię, to dziedziczę hierarchia ze std :: runtime_error (która dziedziczy ze std :: exception). Następnie mogę użyć .what() i użyć jeszcze mniej bloków catch podczas obsługi większej liczby wyjątków.

+0

+1 dla wszystkich przydatnych odpowiedzi. Jestem rozdarty, co jest najbardziej przydatne, ale w końcu postanowiłem to zaakceptować. – Steve314

6

Ten wzór jest zdecydowanie bezpieczny.

Istnieją specjalne zasady przedłużające żywotność rzucanego przedmiotu. Skutecznie, istnieje tak długo, jak długo jest obsługiwany i ma gwarancję, że będzie istniał do końca ostatniego bloku catch, który go obsługuje.

One bardzo często idiom, na przykład w celu uzyskania niestandardowych wyjątki od std::exception, przesłonić jego what() funkcji członka i złapać go przez odniesienie, dzięki czemu można drukować komunikaty o błędach z szerokiej gamy wyjątków z klauzulą ​​jeden haczyk.

4

Tak. Jak na razie dobrze.

Osobiście używam std :: runtime_error jako podstawy wszystkich wyjątków calsses. Obsługuje komunikaty o błędach itp.

Nie należy również zgłaszać dodatkowych wyjątków. Zdefiniuj wyjątek tylko w przypadku rzeczy, które faktycznie mogą zostać przechwycone i naprawione. Użyj bardziej ogólnego wyjątku dla rzeczy, których nie można złapać lub naprawić.

Na przykład: Jeśli opracuję bibliotekę A. Wtedy otrzymam wyjątek AEx pochodzące ze std :: runtime_error. Ten wyjątek zostanie użyty dla wszystkich ogólnych wyjątków z biblioteki. W przypadku konkretnych wyjątków, w których użytkownik biblioteki może faktycznie przechwycić i wykonać coś (naprawić lub złagodzić) za pomocą wyjątku, utworzę specjalny wyjątek wywodzący się z wyjątku AException (ale tylko wtedy, gdy jest coś, co można zrobić z wyjątkiem).

Powiązane problemy