Powinny istnieć twierdzenia w kompilacjach wersji

20

Domyślnym zachowaniem assertw C ++ jest brak działania w kompilacjach wersji. Zakładam, że dzieje się tak ze względu na wydajność i być może w celu uniemożliwienia użytkownikom wyświetlania nieprzyjemnych komunikatów o błędach.

Twierdzę jednak, że sytuacje, w których assertstrzeliłby, ale został wyłączony, są jeszcze bardziej kłopotliwe, ponieważ aplikacja najprawdopodobniej ulegnie awarii w jeszcze gorszym stopniu, ponieważ część niezmiennika została zerwana.

Dodatkowo argument wydajności dla mnie liczy się tylko wtedy, gdy jest to wymierny problem. Większość asserts mojego kodu nie jest dużo bardziej złożona niż

assert(ptr != nullptr);

co będzie miało niewielki wpływ na większość kodu.

To prowadzi mnie do pytania: czy asercje (czyli koncepcja, a nie konkretna implementacja) powinny być aktywne w kompilacjach wersji? Dlaczego nie)?

Należy pamiętać, że to pytanie nie dotyczy sposobu włączania asercji w kompilacjach wersji (takich jak #undef _NDEBUGimplementacja asercji lub korzystanie z niej). Co więcej, nie chodzi o włączanie asercji w kodzie biblioteki trzeciej / standardowym, ale w kodzie kontrolowanym przeze mnie.

Nikt
źródło
Znam globalnego gracza na rynku opieki zdrowotnej, który zainstalował debugową wersję swojego szpitalnego systemu informacyjnego u klientów. Mieli specjalną umowę z Microsoftem na zainstalowanie tam również bibliotek debugujących C ++. Cóż, jakość ich produktu była ...
Bernhard Hiller
2
Istnieje stare powiedzenie, że „usuwanie twierdzeń przypomina trochę noszenie kamizelki ratunkowej podczas ćwiczeń w porcie, ale pozostawianie kamizelek ratunkowych, gdy statek wypływa na otwarty ocean” ( wiki.c2.com/?AssertionsAsDefensiveProgramming ) . Osobiście domyślnie włączam je w kompilacjach wersji. Niestety, ta praktyka nie jest zbyt powszechna w świecie C ++, ale przynajmniej znany weteran C ++ James Kanze zawsze kłócił się o utrzymanie asercji w kompilacjach wersji: stackoverflow.com/a/12162195/3313064
Christian Hackl

Odpowiedzi:

20

Klasyk assertjest narzędziem ze starej standardowej biblioteki C, a nie z C ++. Jest nadal dostępny w C ++, przynajmniej z powodów kompatybilności wstecznej.

Nie mam pod ręką dokładnej osi czasu standardowych bibliotek C, ale jestem prawie pewien, że assertbył dostępny wkrótce po wejściu K&R C na rynek (około 1978 r.). W klasycznym języku C do pisania solidnych programów dodawanie testów wskaźnika NULL i sprawdzanie granic tablic należy przeprowadzać znacznie częściej niż w C ++. Kosztów testów wskaźnika NULL można uniknąć, stosując odniesienia i / lub inteligentne wskaźniki zamiast wskaźników, a przy użyciu std::vectorsprawdzania granic tablicy często nie jest konieczne. Co więcej, wydajność osiągnięta w 1980 roku była zdecydowanie ważniejsza niż dzisiaj. Myślę więc, że jest bardzo prawdopodobne, że „assert” został domyślnie zaprojektowany tak, aby był aktywny tylko w kompilacjach debugowania.

Co więcej, do obsługi prawdziwych błędów w kodzie produkcyjnym funkcja, która po prostu testuje jakiś warunek lub niezmiennik i powoduje awarię programu, jeśli warunek nie jest spełniony, w większości przypadków nie jest wystarczająco elastyczna. W przypadku debugowania jest to prawdopodobnie w porządku, ponieważ ten, kto uruchamia program i zauważa błąd, zwykle ma pod ręką debugger do analizy tego, co się dzieje. Jednak w przypadku kodu produkcyjnego rozsądnym rozwiązaniem musi być funkcja lub mechanizm, który

  • testuje jakiś warunek (i zatrzymuje wykonywanie w zakresie, w którym warunek się nie powiedzie)

  • wyświetla wyraźny komunikat o błędzie w przypadku, gdy warunek się nie utrzymuje

  • pozwala zewnętrznemu zasięgowi na przesłanie komunikatu o błędzie i wyprowadza go do określonego kanału komunikacyjnego. Ten kanał może być czymś w rodzaju stderr, standardowego pliku dziennika, okna komunikatu w programie GUI, ogólnego oddzwaniania do obsługi błędów, kanału błędów w sieci lub czegokolwiek, co najlepiej pasuje do konkretnego oprogramowania.

  • pozwala zewnętrznemu zakresowi na podstawie poszczególnych przypadków zdecydować, czy program powinien zakończyć się z wdziękiem, czy kontynuować.

(Oczywiście są też sytuacje, w których natychmiastowe zakończenie programu w przypadku niespełnienia warunku jest jedyną sensowną opcją, ale w takich przypadkach powinno to nastąpić również w kompilacji wersji, a nie tylko w wersji debugowania).

Od klasyki assert wersja nie udostępnia tych funkcji, nie jest dobrze dopasowana do kompilacji wersji, zakładając, że kompilacja wersji jest tym, co wdraża się w produkcji.

Teraz możesz zapytać, dlaczego w standardowej bibliotece języka C nie ma takiej funkcji ani mechanizmu, który zapewnia taką elastyczność. W rzeczywistości w C ++ istnieje standardowy mechanizm, który ma wszystkie te funkcje (i więcej) i wiesz o tym: nazywa się to wyjątkami .

Jednak w języku C trudno jest wdrożyć dobry, uniwersalny mechanizm ogólnego przeznaczenia do obsługi błędów ze wszystkimi wymienionymi funkcjami z powodu braku wyjątków jako części języka programowania. Dlatego większość programów w C ma własne mechanizmy obsługi błędów z kodami powrotu, „goto”, „długimi skokami” lub ich kombinacją. Są to często pragmatyczne rozwiązania, które pasują do określonego rodzaju programu, ale nie są „wystarczająco ogólne, aby zmieściły się w bibliotece standardowej C.

Doktor Brown
źródło
Ach, zupełnie zapomniałem o dziedzictwie C (mimo wszystko #include <cassert>). To całkowicie ma teraz sens.
Nikt
1
Całkowicie nie zgadzam się z tą całą odpowiedzią. Zgłoszenie wyjątku, gdy aplikacja wykryje, że jej kod źródłowy był wadliwy, jest ostatecznością w językach takich jak Java, które nie mogą sobie poradzić lepiej, ale w C ++, w każdym przypadku, natychmiast zamknij program w takiej sytuacji. Nie chcesz stos odwijania powodować żadnych dalszych czynności należy wykonać w tym momencie. Błędny program może zapisywać potencjalnie uszkodzone dane do plików, baz danych lub gniazd sieciowych. Po prostu zawiesz się JAK NAJSZYBCIEJ (i ewentualnie zrestartuj proces przez zewnętrzny mechanizm).
Christian Hackl,
1
@ChristianHackl: Zgadzam się, że istnieją sytuacje, w których zakończenie programu jest jedyną realną opcją, ale wtedy powinno się to zdarzyć również w trybie zwolnienia i assertjest również niewłaściwym narzędziem do tego (zgłoszenie wyjątku może być rzeczywiście niewłaściwą reakcją ). Jestem jednak całkiem pewien, że w rzeczywistości assertbył również używany do wielu testów w C, gdzie w C ++ można by dziś używać wyjątków.
Doc Brown
15

Jeśli okaże się, że chcesz, aby aserty były włączone w wydaniu, które poprosiłeś, aby wykonały złą pracę.

Chodzi o to, że nie są włączone w wydaniu. Pozwala to na testowanie niezmienników podczas programowania przy użyciu kodu, który w innym przypadku musiałby być kodem rusztowania. Kod, który należy usunąć przed wydaniem.

Jeśli masz coś, co Twoim zdaniem powinno zostać przetestowane nawet podczas wydania, napisz kod, który to przetestuje. TheIf throwKonstrukt działa bardzo dobrze. Jeśli chcesz powiedzieć coś innego niż inne rzuty, po prostu użyj opisowego wyjątku, który mówi, co chcesz powiedzieć.

Nie chodzi o to, że nie możesz zmienić sposobu, w jaki używasz zapewnień. Chodzi o to, że nie przynosi ci to nic użytecznego, jest sprzeczne z oczekiwaniami i nie pozostawia w żaden sposób czystego sposobu robienia tego, co powinny robić twierdzenia. Dodaj testy, które są nieaktywne w wydaniu.

Nie mówię o konkretnej realizacji asercji, ale o koncepcji asercji. Nie chcę nadużywać asercji ani dezorientować czytelników. Chciałem przede wszystkim zapytać, dlaczego tak jest. Dlaczego nie ma dodatkowych release_assert? Czy nie ma takiej potrzeby? Jakie jest uzasadnienie twierdzenia o wyłączeniu w wydaniu? - Nikt

Dlaczego nie ma parametru relase_assert? Szczerze mówiąc, ponieważ twierdzenia nie są wystarczające do produkcji. Tak, istnieje potrzeba, ale nic nie spełnia tej potrzeby. Och, na pewno możesz zaprojektować własny. Mechanicznie twoja funkcja throwIf wymaga po prostu bool i wyjątku do rzucenia. I to może pasować do twoich potrzeb. Ale tak naprawdę ograniczasz projekt. Dlatego nie dziwi mnie, że w twojej bibliotece języków nie ma systemu zgłaszania wyjątków. Z pewnością nie jest tak, że nie możesz tego zrobić. Inni mają . Ale zajmowanie się przypadkiem, w którym coś idzie nie tak, to 80% pracy dla większości programów. I jak dotąd nikt nie pokazał nam dobrego, uniwersalnego rozwiązania dla wszystkich rozwiązań. Skuteczne radzenie sobie z tymi przypadkami może się skomplikować. Gdybyśmy mieli puszkowany system release_assert, który nie spełniałby naszych potrzeb, myślę, że przyniósłby więcej szkody niż pożytku. Prosisz o dobrą abstrakcję, która oznaczałaby, że nie musiałbyś myśleć o tym problemie. Ja też chcę, ale nie wygląda na to, żebyśmy tam byli.

Dlaczego twierdzenia są wyłączone w wydaniu? Aserty powstały u szczytu wieku kodu rusztowania. Kod, który musieliśmy usunąć, ponieważ wiedzieliśmy, że nie chcemy go w produkcji, ale wiedzieliśmy, że chcemy uruchomić programowanie, aby pomóc nam znaleźć błędy. Aserty były czystszą alternatywą dla if (DEBUG)wzorca, który pozwala nam pozostawić kod, ale go wyłączyć. Było to przed rozpoczęciem testów jednostkowych jako głównego sposobu na oddzielenie kodu testowego od kodu produkcyjnego. Aserty są nadal używane, nawet przez ekspertów testujących jednostki, zarówno w celu wyjaśnienia oczekiwań, jak i uwzględnienia przypadków, w których są one lepsze niż testy jednostkowe.

Dlaczego nie zostawić kodu debugowania w produkcji? Ponieważ kod produkcyjny nie powinien zawstydzać firmy, nie formatować dysku twardego, nie uszkadzać bazy danych i nie wysyłać wiadomości e-mail do prezesa. Krótko mówiąc, miło jest móc pisać kod debugujący w bezpiecznym miejscu, w którym nie musisz się o to martwić.

candied_orange
źródło
2
Moje pytanie brzmi: dlaczego ten kod należy usunąć przed wydaniem? Kontrole nie powodują znacznego obniżenia wydajności, a jeśli się nie powiedzie, zdecydowanie pojawia się problem, o którym wolałbym bardziej bezpośredni komunikat błędu. Jakie korzyści oferuje mi wyjątek zamiast twierdzenia? Prawdopodobnie nie chciałbym, aby niepowiązany kod był w stanie to catch(...)zrobić.
Nikt
2
@ Nikt Największą zaletą nieużywania asercji do niepotwierdzonych potrzeb jest niezrozumienie czytelników. Jeśli nie chcesz, aby kod był wyłączony, nie używaj idiomów sygnalizujących, że tak będzie. Jest to tak złe, jak używanie if (DEBUG)do kontrolowania czegoś innego niż kod debugowania. Mikrooptymalizacja może w twoim przypadku nic nie znaczyć. Ale ten idiom nie powinien być obalony tylko dlatego, że go nie potrzebujesz.
candied_orange
Myślę, że nie wyraziłem wystarczająco jasno mojej intencji. Nie mówię o konkretnej realizacji, assertale o koncepcji twierdzenia. Nie chcę nadużywać assertani dezorientować czytelników. Chciałem przede wszystkim zapytać, dlaczego tak jest. Dlaczego nie ma żadnych dodatkowych release_assert? Czy nie ma takiej potrzeby? Jakie jest uzasadnienie twierdzenia o wyłączeniu w wydaniu?
Nikt
2
You're asking for a good abstraction.Nie jestem tego pewny. Chcę przede wszystkim poradzić sobie z problemami, których nie można odzyskać i które nigdy nie powinny się zdarzyć. Weź to razem z, Because production code needs to [...] not format the hard drive [...]i powiedziałbym, że w przypadkach, w których niezmienniki są zepsute, wezmę w dowolnym momencie uwagę na UB.
Nikt
1
@Nikt „problemom, w których nie można odzyskać”, można zaradzić, zgłaszając wyjątki i nie łapiąc ich. Możesz zauważyć, że nigdy nie powinny się zdarzyć w tekście komunikatu wyjątku.
candied_orange
4

Asercje to narzędzie do debugowania , a nie technika programowania defensywnego. Jeśli chcesz przeprowadzić sprawdzanie poprawności we wszystkich okolicznościach, wykonaj sprawdzanie poprawności, pisząc polecenie warunkowe - lub utwórz własne makro, aby zmniejszyć płytkę kotłową.

amon
źródło
4

assertjest formą dokumentacji, podobnie jak komentarze. Podobnie jak komentarze, zwykle nie wysyłałbyś ich do klientów - nie należą one do kodu wersji.

Ale problem z komentarzami polega na tym, że mogą stać się nieaktualne, a jednak pozostaną. Dlatego twierdzenia są dobre - sprawdzane są w trybie debugowania. Gdy assert stanie się przestarzały, szybko go odkryjesz i nadal będziesz wiedział, jak go naprawić. Ten komentarz, który stał się nieaktualny 3 lata temu? Zgadnijcie.

MSalters
źródło
1
Czy więc przerwanie nieudanego niezmiennika, zanim coś się stanie, nie jest uzasadnionym przypadkiem użycia?
Nikt
3

Jeśli nie chcesz, aby „twierdzenie” było wyłączone, możesz napisać prostą funkcję, która ma podobny efekt:

void fail_if(bool b) {if(!b) std::abort();}

Oznacza to, że assertjest na testach, gdzie zrobić chcą im odejść w dostarczonym produkcie. Jeśli chcesz, aby ten test był częścią określonego zachowania programu, assertjest to niewłaściwe narzędzie.

Nicol Bolas
źródło
1
Jestem tego świadomy. Pytanie jest bardziej zgodne z tym, dlaczego domyślna jest taka, jaka jest.
Nikt
3

Nie ma sensu argumentować, co powinien zrobić aser, robi to, co robi. Jeśli chcesz czegoś innego, napisz własną funkcję. Na przykład mam Assert, który zatrzymuje się w debuggerze lub nic nie robi, mam AssertFatal, który spowoduje awarię aplikacji, mam funkcje boolowe Asserted i AssertionFailed, które potwierdzają i zwracają wynik, dzięki czemu mogę zarówno potwierdzić, jak i poradzić sobie z sytuacją.

W przypadku każdego nieoczekiwanego problemu musisz zdecydować, jak najlepiej poradzić sobie zarówno programista, jak i użytkownik.

gnasher729
źródło
Nie miałem zamiaru kłócić się o to, co należy zrobić. Moją motywacją do tego pytania było znalezienie uzasadnienia twierdzeń, które nie znajdują się w kompilacji wersji, ponieważ zamierzam napisać własne i chcę zebrać informacje na temat przypadków użycia, oczekiwań i pułapek.
Nikt
Hm, czy nie verify(cond)byłby to normalny sposób na potwierdzenie i zwrócenie wyniku?
Deduplicator
1
Piszę w Ruby, nie w C, ale nie zgadzam się z większością powyższych odpowiedzi, ponieważ zatrzymanie programu z drukowaniem śladu wstecznego działa dla mnie dobrze jako technika programowania obronnego ... mój komentarz nie pasował do maksymalny rozmiar komentarza w znakach, więc napisałem inną odpowiedź poniżej.
Nakilon
2

Jak zauważyli inni, assertjest to twój ostatni bastion obrony przed błędami programisty, które nigdy nie powinny się zdarzyć. Są to kontrole poczytalności, które, miejmy nadzieję, nie powinny zawieść w lewo i w prawo do czasu wysyłki.

Został również zaprojektowany tak, aby można go było pominąć w wersjach stabilnych, bez względu na powody, dla których programiści mogą się okazać przydatni: estetykę, wydajność, cokolwiek chcą. Jest to część tego, co odróżnia kompilację debugowania od kompilacji wersji, a z definicji kompilacja wersji jest pozbawiona takich twierdzeń. Jest więc odwrócenie tego projektu, jeśli chcesz wydać analogiczną „kompilację wydania z zapewnieniami w miejscu”, która byłaby próbą kompilacji wersji z _DEBUGdefinicją preprocesora i nie NDEBUGzdefiniowaną; tak naprawdę nie jest to już kompilacja wydania.

Projekt rozciąga się nawet na standardową bibliotekę. W bardzo prosty przykład wśród wielu, wielu implementacjach std::vector::operator[]będzie assertsprawdzenia dokonywane aby upewnić się, że nie sprawdzasz wektor poza granicami. Biblioteka standardowa zacznie działać znacznie, znacznie gorzej, jeśli włączysz takie kontrole w kompilacji wydania. Punkt odniesienia vectorprzy użyciuoperator[]a wypełnienie ctora takimi twierdzeniami zawartymi w stosunku do zwykłej starej tablicy dynamicznej często pokazuje, że tablica dynamiczna jest znacznie szybsza, dopóki nie wyłączysz takich kontroli, więc często wpływają one na wydajność w daleki, daleki od trywialnych sposobów. Kontrola zerowego wskaźnika tutaj i kontrola poza zakresem może faktycznie stać się ogromnym wydatkiem, jeśli takie kontrole są stosowane miliony razy w każdej ramce w krytycznych pętlach poprzedzających kod tak prosty, jak usunięcie dereferencji z inteligentnego wskaźnika lub dostęp do tablicy.

Więc najprawdopodobniej potrzebujesz innego narzędzia do tego zadania i takiego, które nie zostało zaprojektowane tak, aby było pominięte w kompilacjach wersji, jeśli chcesz kompilacje wersji, które wykonują takie testy poprawności w kluczowych obszarach. Najbardziej przydatne dla mnie osobiście jest logowanie. W takim przypadku, gdy użytkownik zgłasza błąd, staje się znacznie łatwiej, jeśli dołącza dziennik, a ostatnia linia dziennika daje mi dużą wskazówkę, gdzie wystąpił błąd i co może być. Następnie, po odtworzeniu ich kroków w kompilacji debugowania, mogę również otrzymać błąd asercji, a ta awaria asercji dodatkowo daje mi ogromne wskazówki, aby usprawnić mój czas. Ponieważ jednak rejestrowanie jest stosunkowo drogie, nie używam go do przeprowadzania kontroli poczytalności na bardzo niskim poziomie, takich jak upewnienie się, że tablica nie jest dostępna poza granicami w ogólnej strukturze danych.

Jednak w końcu, w pewnym stopniu w zgodzie z tobą, widziałem rozsądny przypadek, w którym możesz wręczać testerom coś przypominającego kompilację debugowania podczas testów alfa, na przykład z małą grupą testerów alfa, którzy, powiedzmy, podpisali umowę NDA . Tam może usprawnić testy alfa, jeśli podasz testerom coś innego niż pełną wersję wydania z dołączonymi informacjami o debugowaniu wraz z niektórymi funkcjami debugowania / programowania, takimi jak testy, które można uruchomić i bardziej szczegółowe wyniki podczas uruchamiania oprogramowania. Przynajmniej widziałem, jak niektóre duże firmy produkujące gry robią takie rzeczy dla alfa. Ale dotyczy to testów alfa lub testów wewnętrznych, w których naprawdę próbujesz dać testerom coś innego niż kompilację wydania. Jeśli faktycznie próbujesz wysłać kompilację wersji, to z definicji nie powinna_DEBUG zdefiniowane lub to naprawdę mylące różnice między kompilacją „debugowania” i „wydania”.

Dlaczego ten kod należy usunąć przed wydaniem? Kontrole nie powodują znacznego obniżenia wydajności, a jeśli się nie powiedzie, zdecydowanie pojawia się problem, o którym wolałbym bardziej bezpośredni komunikat błędu.

Jak wskazano powyżej, kontrole niekoniecznie są trywialne z punktu widzenia wydajności. Wiele z nich jest prawdopodobnie trywialnych, ale ponownie, nawet standardowa biblioteka lib używa ich i może to wpłynąć na wydajność w nieakceptowalny sposób dla wielu osób w wielu przypadkach, jeśli, powiedzmy, przechodzenie losowego dostępu std::vectorzajęło 4 razy więcej czasu, co powinno być zoptymalizowaną wersją wydania ze względu na sprawdzanie granic, które nigdy nie powinno zawieść.

W poprzednim zespole faktycznie musieliśmy sprawić, by nasza macierz i biblioteka wektorowa wykluczały niektóre twierdzenia na niektórych krytycznych ścieżkach, aby przyspieszyć kompilacje debugowania, ponieważ te spowalniały operacje matematyczne o ponad rząd wielkości do punktu, w którym było zaczynamy wymagać od nas czekania 15 minut, zanim będziemy mogli wczytać się w interesujący kod. Moi koledzy naprawdę chcieli po prostu usunąćassertswprost, ponieważ odkryli, że samo to robi ogromną różnicę. Zamiast tego postanowiliśmy uniknąć krytycznych ścieżek debugowania. Kiedy sprawiliśmy, że te ścieżki krytyczne wykorzystywały dane wektorowe / macierzowe bezpośrednio, bez przechodzenia przez sprawdzanie granic, czas wymagany do wykonania pełnej operacji (która obejmowała więcej niż tylko matematykę wektor / macierz) skrócił się z minut do sekund. Jest to więc skrajny przypadek, ale zdecydowanie twierdzenia nie zawsze są nieistotne z punktu widzenia wydajności, a nawet bliskie.

Ale to także sposób, w jaki assertszostały zaprojektowane. Jeśli nie miały one tak dużego wpływu na wydajność na całym forum, mógłbym go faworyzować, gdyby zostały zaprojektowane jako coś więcej niż funkcja kompilacji debugowania lub moglibyśmy użyć tej funkcji, vector::atktóra obejmuje sprawdzanie granic nawet w kompilacjach wersji i wyrzucanie poza granice dostęp, np. (ale z ogromnym hitem wydajności). Ale obecnie uważam, że ich projekt jest znacznie bardziej użyteczny, biorąc pod uwagę ich ogromny wpływ na wydajność w moich przypadkach, jako funkcję tylko do kompilacji debugowania, która jest pomijana, gdy NDEBUGjest zdefiniowana. Przynajmniej w przypadku przypadków, z którymi pracowałem, w wydaniu kompilacji jest ogromna różnica, ponieważ wyklucza sprawdzanie poprawności, które nigdy nie powinno zawieść.

vector::at vs. vector::operator[]

Myślę, że rozróżnienie tych dwóch metod stanowi sedno tego, a także alternatywy: wyjątków. vector::operator[]implementacje zwykle assertw celu upewnienia się, że dostęp poza granicami wyzwoli łatwo powtarzalny błąd podczas próby uzyskania dostępu do wektora poza granicami. Ale implementatorzy bibliotek robią to przy założeniu, że nie będzie to kosztować ani grosza w zoptymalizowanej wersji wydania.

Tymczasem vector::atzapewnione jest to, że zawsze sprawdza się poza limitem i rzuca nawet w kompilacjach wersji, ale ma to negatywny wpływ na wydajność do tego stopnia, że ​​często widzę o wiele więcej kodu używającego vector::operator[]niż vector::at. Wiele projektów C ++ przypomina ideę „płacić za to, czego używasz / potrzebujesz”, a wiele osób często faworyzuje operator[], co nawet nie przeszkadza w sprawdzaniu granic w kompilacjach wersji, w oparciu o przekonanie, że nie nie trzeba sprawdzać granic w ich zoptymalizowanych kompilacjach wersji. Nagle, jeśli w kompilacjach wersji włączono asercje, wydajność tych dwóch będzie identyczna, a użycie wektora zawsze będzie wolniejsze niż tablicy dynamicznej. Tak więc ogromna część projektu i korzyści asercji opiera się na pomyśle, że stają się one darmowe w kompilacji wersji.

release_assert

Jest to interesujące po odkryciu tych intencji. Oczywiście przypadki użycia dla każdego byłyby inne, ale myślę, że znajdę jakieś zastosowanie dla tego, release_assertktóry sprawdza i powoduje awarię oprogramowania, pokazując numer linii i komunikat o błędzie, nawet w kompilacjach wersji.

Byłoby tak w przypadku niektórych niejasnych przypadków, w których nie chcę, aby oprogramowanie w pełni odzyskało wydajność, tak jak w przypadku zgłoszenia wyjątku. Chciałbym, aby w takich przypadkach zawiesił się, aby użytkownik mógł otrzymać numer linii, aby zgłosić, gdy oprogramowanie napotkało coś, co nigdy nie powinno się zdarzyć, nadal w sferze kontroli poprawności pod kątem błędów programisty, a nie zewnętrznych błędów wejściowych, takich jak wyjątki, ale na tyle tanie, że można to zrobić bez obawy o koszty wydania.

W rzeczywistości istnieją przypadki, w których znajdę poważną awarię z numerem linii i komunikatem o błędzie, które lepiej jest odzyskać od zgłoszonego wyjątku, który może być na tyle tani, aby utrzymać się w wydaniu. Są też przypadki, w których niemożliwe jest odzyskanie z wyjątku, na przykład błąd napotkany podczas próby odzyskania z istniejącego. Tam znalazłem idealne dopasowanie release_assert(!"This should never, ever happen! The software failed to fail!");i naturalnie byłoby tanie, ponieważ kontrola byłaby przeprowadzana przede wszystkim na wyjątkowej ścieżce i nie kosztowałaby nic w normalnych ścieżkach wykonania.


źródło
Moim zamiarem nie było włączanie asercji w kodzie strony trzeciej, patrz wyjaśnienie edycji. Cytat z mojego komentarza pomija kontekst mojego pytania: Additionally, the performance argument for me only counts when it is a measurable problem.Więc chodzi o to, aby zdecydować na podstawie każdego przypadku, kiedy wyciągnąć twierdzenie z wydania.
Nikt
Jednym z problemów związanych z ostatnim komentarzem jest to, że biblioteka standardowa używa tego samego, assertktóry opiera się na tych samych _DEBUG/NDEBUGdefinicjach preprocesora, jak to, czego użyłbyś podczas używania assertmakra. Nie ma więc możliwości selektywnego włączania asercji tylko dla jednego fragmentu kodu bez włączenia go dla standardowej biblioteki lib, np. Zakładając, że używa standardowej biblioteki lib.
Sprawdzane jest iterator STL, który można osobno wyłączyć, co wyłącza niektóre z twierdzeń, ale nie wszystkie.
Zasadniczo jest to zgrubne narzędzie, a nie szczegółowe, i zawsze z takim założeniem, że projekt zostanie wyłączony w wydaniu, więc koledzy z zespołu mogą go używać w obszarach krytycznych wbrew założeniu, że nie wpłynie to na wydajność wydania, standardowa biblioteka używa tego, że w krytycznych obszarach, biblioteki innych firm używają go w ten sposób itp. A jeśli chcesz czegoś, możesz wyłączyć / włączyć bardziej konkretnie jeden fragment kodu, a nie drugi, wracamy do patrzenia na coś innego niż assert.
vector::operator[]i vector::at, na przykład, miałby praktycznie taką samą wydajność, gdyby asercje były włączone w kompilacjach wersji, i nie byłoby sensu operator[]więcej używać z punktu widzenia wydajności, ponieważ i tak sprawdzałby granice.
2

Piszę w Ruby, a nie w C / C ++, więc nie będę mówił o różnicy między twierdzeniami a wyjątkami, ale chciałbym mówić o tym jako o rzeczy, która zatrzymuje środowisko uruchomieniowe . Nie zgadzam się z większością powyższych odpowiedzi, ponieważ zatrzymanie programu z drukowaniem śladu wstecznego działa dla mnie dobrze jako technika programowania obronnego.
Jeśli istnieje sposób na wywołanie rutyny (absolutnie bez względu na to, jak jest ona składniowo i jeśli słowo „aser” jest używane lub istnieje w języku programowania lub dsl), oznacza to, że należy wykonać pewne czynności i produkt natychmiast wraca z „wydanej, gotowej do użycia” do „potrzebuje poprawki” - teraz albo przepisz ją do obsługi wyjątków, albo napraw błąd, który spowodował pojawienie się niewłaściwych danych.

Mam na myśli, że Assert nie jest czymś, z czym powinieneś często mieszkać i dzwonić - to sygnał stopu, który wskazuje, że powinieneś zrobić coś, aby to się nigdy więcej nie powtórzyło. A powiedzenie, że „wydanie kompilacji nie powinno mieć asercji” jest jak powiedzenie „wydanie kompilacji nie powinno zawierać błędów” - koleś, prawie niemożliwe jest uporanie się z tym.
Lub pomyśl o nich jako o „niepowodzeniu niezastosowanych i wykonanych przez użytkownika końcowego”. Nie możesz przewidzieć wszystkich rzeczy, które użytkownik zrobi z twoim programem, ale jeśli coś zbyt poważnego pójdzie nie tak, powinno się to zatrzymać - jest to podobne do sposobu budowania potoków kompilacji - zatrzymujesz proces i nie publikujesz, prawda? ? Asercja zmusza użytkownika do zatrzymania, zgłoszenia i oczekiwania na pomoc.

Nakilon
źródło