Mam tendencję do dodawania wielu asercji do mojego kodu C ++, aby ułatwić debugowanie bez wpływu na wydajność kompilacji wydania. Teraz assert
jest czystym makrem C, zaprojektowanym bez uwzględnienia mechanizmów C ++.
Z drugiej strony C ++ definiuje std::logic_error
, który ma być wyrzucany w przypadkach, gdy występuje błąd w logice programu (stąd nazwa). Rzucanie instancji może być po prostu doskonałą, bardziej C ++ alternatywną alternatywą dla assert
.
Problem polega na tym, że assert
i abort
oba kończą program natychmiast bez wywoływania destruktorów, pomijając w ten sposób czyszczenie, podczas gdy ręczne zgłaszanie wyjątku powoduje niepotrzebne koszty działania. Jednym ze sposobów obejścia tego byłoby utworzenie własnego makra asercji SAFE_ASSERT
, które działa tak samo jak odpowiednik w C, ale zgłasza wyjątek w przypadku niepowodzenia.
Przychodzą mi na myśl trzy opinie na ten temat:
- Trzymaj się twierdzenia C. Ponieważ program jest natychmiast przerywany, nie ma znaczenia, czy zmiany zostały poprawnie rozwinięte. Również używanie
#define
s w C ++ jest równie złe. - Wrzuć wyjątek i złap go w main () . Pozwalanie kodowi na pomijanie destruktorów w dowolnym stanie programu jest złą praktyką i należy tego unikać za wszelką cenę, podobnie jak wywołania terminate (). Jeśli rzucane są wyjątki, muszą zostać złapane.
- Zgłoś wyjątek i pozwól mu zakończyć program. Wyjątek kończący program jest w porządku iz tego powodu
NDEBUG
nigdy nie nastąpi to w kompilacji wydania. Przechwytywanie jest niepotrzebne i ujawnia szczegóły implementacji kodu wewnętrznegomain()
.
Czy istnieje ostateczna odpowiedź na ten problem? Jakieś profesjonalne referencje?
Edytowano: pomijanie destruktorów nie jest oczywiście niezdefiniowanym zachowaniem.
źródło
logic_error
jest to błąd logiczny. Błąd w logice programu nazywa się błędem. Nie rozwiązujesz błędów, rzucając wyjątki.static_assert
tam, gdzie jest to właściwe, jeśli masz to dostępne.std::bug
?std::abort()
; po prostu podniesie sygnał, który spowoduje zakończenie procesu.Odpowiedzi:
Asercje są całkowicie odpowiednie w kodzie C ++. Wyjątki i inne mechanizmy obsługi błędów nie są tak naprawdę przeznaczone do tego samego, co asercje.
Obsługa błędów ma zastosowanie, gdy istnieje możliwość odzyskania lub zgrabnego zgłoszenia błędu użytkownikowi. Na przykład, jeśli wystąpi błąd podczas próby odczytania pliku wejściowego, możesz chcieć coś z tym zrobić. Błędy mogą wynikać z błędów, ale mogą też być po prostu odpowiednim wyjściem dla danego wejścia.
Asercje służą do takich rzeczy, jak sprawdzanie, czy wymagania API są spełnione, gdy normalnie API nie byłoby sprawdzane, lub do sprawdzania rzeczy, które deweloper uważa, że gwarantuje to konstrukcja. Na przykład, jeśli algorytm wymaga posortowanych danych wejściowych, normalnie nie sprawdzałbyś tego, ale możesz mieć asercję, aby to sprawdzić, aby debugowanie kompilowało flagę tego rodzaju błędu. Stwierdzenie powinno zawsze wskazywać na nieprawidłowo działający program.
Jeśli piszesz program, w którym nieczyste zamknięcie może spowodować problem, możesz chcieć uniknąć asercji. Niezdefiniowane zachowanie ściśle w zakresie języka C ++ nie kwalifikuje się tutaj jako taki problem, ponieważ trafienie w asercję jest prawdopodobnie już wynikiem niezdefiniowanego zachowania lub naruszenia jakiegoś innego wymagania, które mogłoby uniemożliwić poprawne działanie niektórych porządków.
Również jeśli zaimplementujesz asercje w kategoriach wyjątku, może to potencjalnie zostać przechwycone i „obsłużone”, nawet jeśli jest to sprzeczne z samym celem stwierdzenia.
źródło
3
zamiast1
do twojego kodu, generalnie nie powinno to wyzwalać asercji. Asercje są tylko błędem programisty, a nie błędem użytkownika biblioteki lub aplikacji.Asercje służą do debugowania . Użytkownik wysłanego kodu nigdy nie powinien ich widzieć. Jeśli dojdzie do asercji, Twój kod musi zostać naprawiony.
CWE-617: Reachable Assertion
Wyjątki dotyczą wyjątkowych okoliczności . Jeśli zostanie napotkany, użytkownik nie będzie mógł robić tego, co chce, ale może wznowić działanie w innym miejscu.
Obsługa błędów dotyczy normalnego przebiegu programu. Na przykład, jeśli poprosisz użytkownika o numer i otrzymasz coś, czego nie można przeanalizować, jest to normalne , ponieważ dane wejściowe użytkownika nie są pod twoją kontrolą i zawsze musisz zawsze radzić sobie ze wszystkimi możliwymi sytuacjami. (Np. Zapętlaj, aż uzyskasz prawidłowe dane wejściowe, mówiąc „Przepraszamy, spróbuj ponownie” w międzyczasie).
źródło
Asercje mogą być używane do weryfikacji wewnętrznych niezmienników implementacji, takich jak stan wewnętrzny przed lub po wykonaniu jakiejś metody, itp. Jeśli asercja nie powiedzie się, oznacza to, że logika programu jest zepsuta i nie można tego naprawić. W takim przypadku najlepsze, co możesz zrobić, to jak najszybciej się zepsuć bez przekazywania wyjątku użytkownikowi. Naprawdę fajne w asercjach (przynajmniej w Linuksie) jest to, że zrzut pamięci jest generowany w wyniku zakończenia procesu, a zatem można łatwo zbadać ślad stosu i zmienne. Jest to znacznie bardziej przydatne do zrozumienia błędu logiki niż komunikatu o wyjątku.
źródło
Brak działania destruktorów z powodu allingu abort () nie jest niezdefiniowanym zachowaniem!
Gdyby tak było, wywołanie
std::terminate()
również byłoby niezdefiniowanym zachowaniem , a więc jaki byłby sens w ich zapewnianiu?assert()
jest tak samo przydatna w C ++, jak w C. Asercje nie służą do obsługi błędów, służą do natychmiastowego przerywania programu.źródło
abort()
za natychmiastowe przerwanie programu. Masz rację, że asercje nie służą do obsługi błędów, ale assert próbuje obsłużyć błąd przez przerwanie. Czy nie powinieneś zamiast tego zgłosić wyjątku i pozwolić wywołującemu obsłużyć błąd, jeśli to możliwe? W końcu wywołujący jest w lepszej pozycji, aby określić, czy awaria jednej funkcji sprawia, że nie warto robić nic innego. Być może dzwoniący próbuje wykonać trzy niezwiązane ze sobą rzeczy i nadal może ukończyć pozostałe dwa zadania i po prostu odrzucić to.assert
jest zdefiniowany do wywołaniaabort
(gdy warunek jest fałszywy). Jeśli chodzi o rzucanie wyjątków, nie, nie zawsze jest to właściwe. Niektóre rzeczy nie mogą być obsługiwane przez dzwoniącego. Wzywający nie może określić, czy błąd logiczny w funkcji biblioteki innej firmy można odzyskać lub czy można naprawić uszkodzone dane.IMHO, asercje służą do sprawdzania warunków, które jeśli zostaną naruszone, sprawią, że wszystko inne będzie bezsensowne. I dlatego nie możesz po nich wyzdrowieć, a raczej odzyskiwanie jest nieistotne.
Pogrupowałbym je w 2 kategorie:
Są to trywialne przykłady, ale niezbyt odległe od rzeczywistości. Na przykład pomyśl o naiwnych algorytmach, które zwracają ujemne indeksy do użycia z wektorami. Lub programy osadzone w niestandardowym sprzęcie. A raczej dlatego, że dzieje się gówno .
A jeśli wystąpią takie błędy programistyczne, nie powinieneś mieć pewności co do zaimplementowanego mechanizmu odzyskiwania lub obsługi błędów. To samo dotyczy błędów sprzętowych.
źródło