Sollte es Aussagen in Release-Builds geben?

20

Das Standardverhalten assertin C ++ besteht darin, in Release-Builds nichts zu tun. Ich gehe davon aus, dass dies aus Gründen der Leistung und möglicherweise um zu verhindern, dass Benutzer unangenehme Fehlermeldungen sehen.

Ich würde jedoch argumentieren, dass die Situationen, in denen eine assertausgelöst hätte, aber deaktiviert war, noch problematischer sind, da die Anwendung dann wahrscheinlich noch schlimmer abstürzt, da eine Invariante defekt ist.

Außerdem zählt das Leistungsargument für mich nur, wenn es sich um ein messbares Problem handelt. Die meisten asserts in meinem Code sind nicht viel komplexer als

assert(ptr != nullptr);

Dies hat nur geringe Auswirkungen auf den meisten Code.

Dies führt mich zu der Frage: Sollten Assertions (dh das Konzept, nicht die spezifische Implementierung) in Release-Builds aktiv sein? Warum nicht)?

Bitte beachten Sie, dass es bei dieser Frage nicht darum geht, wie Sie Asserts in Release-Builds aktivieren (wie #undef _NDEBUGoder mithilfe einer selbst definierten Assert-Implementierung). Darüber hinaus geht es nicht darum, Asserts in Drittanbieter- / Standardbibliothekscode zu aktivieren, sondern in von mir gesteuertem Code.

Niemand
quelle
Ich kenne einen Global Player auf dem Gesundheitsmarkt, der eine Debug-Version seines Krankenhausinformationssystems bei Kunden installiert hat. Sie hatten eine spezielle Vereinbarung mit Microsoft, um die C ++ - Debug-Bibliotheken auch dort zu installieren. Nun, die Qualität ihres Produkts war ...
Bernhard Hiller
2
Es gibt ein altes Sprichwort: "Das Entfernen von Behauptungen ist ein bisschen wie das Tragen einer Schwimmweste, um im Hafen zu üben, aber dann die Schwimmwesten zurückzulassen, wenn Ihr Schiff in Richtung offener Ozean fährt " ( wiki.c2.com/?AssertionsAsDefensiveProgramming ). . Ich persönlich halte sie standardmäßig in Release-Builds aktiviert. Leider ist diese Praxis in der C ++ - Welt nicht sehr verbreitet, aber immerhin hat sich der bekannte C ++ - Veteran James Kanze dafür ausgesprochen
Christian Hackl

Antworten:

20

Der Klassiker assertist ein Tool aus der alten Standard-C-Bibliothek, nicht aus C ++. Zumindest aus Gründen der Abwärtskompatibilität ist es weiterhin in C ++ verfügbar.

Ich habe keine genaue Zeitleiste der C-Standard-Bibliotheken zur Hand, aber ich bin mir ziemlich sicher, dass assertsie kurz nach dem Start von K & R C (um 1978) verfügbar waren. In klassischem C muss zum Schreiben robuster Programme das Hinzufügen von NULL-Zeigertests und die Überprüfung der Array-Grenzen viel häufiger durchgeführt werden als in C ++. Das Brutto von NULL-Zeigertests kann vermieden werden, indem Referenzen und / oder intelligente Zeiger anstelle von Zeigern verwendet werden, und indem die std::vectorÜberprüfung der Array-Grenzen häufig nicht erforderlich ist. Darüber hinaus war der Performance-Hit von 1980 definitiv viel wichtiger als heute. Ich denke, das ist sehr wahrscheinlich der Grund, warum "assert" standardmäßig nur für Debug-Builds aktiviert wurde.

Darüber hinaus ist für eine echte Fehlerbehandlung im Produktionscode eine Funktion, die nur eine Bedingung oder Invariante testet und das Programm abstürzt, wenn die Bedingung nicht erfüllt ist, in den meisten Fällen nicht flexibel genug. Für das Debuggen ist das wahrscheinlich in Ordnung, da derjenige, der das Programm ausführt und den Fehler beobachtet, normalerweise einen Debugger zur Hand hat, um zu analysieren, was passiert. Für den Seriencode muss eine sinnvolle Lösung jedoch eine Funktion oder ein Mechanismus sein, der

  • testet eine Bedingung (und stoppt die Ausführung in dem Bereich, in dem die Bedingung fehlschlägt)

  • Gibt eine eindeutige Fehlermeldung aus, falls die Bedingung nicht erfüllt ist

  • Ermöglicht dem äußeren Bereich, die Fehlermeldung aufzunehmen und an einen bestimmten Kommunikationskanal auszugeben. Dieser Kanal kann so etwas wie stderr, eine Standardprotokolldatei, ein Meldungsfeld in einem GUI-Programm, ein allgemeiner Rückruf zur Fehlerbehandlung, ein netzwerkfähiger Fehlerkanal oder etwas sein, das am besten zu der jeweiligen Software passt.

  • Hiermit kann der äußere Bereich auf Einzelfallbasis entscheiden, ob das Programm ordnungsgemäß beendet oder fortgesetzt werden soll.

(Natürlich gibt es auch Situationen, in denen das sofortige Beenden des Programms im Falle einer nicht erfüllten Bedingung die einzig sinnvolle Option ist. In solchen Fällen sollte dies jedoch auch in einem Release-Build und nicht nur in einem Debug-Build geschehen.)

Da der Klassiker assertdiese Funktionen nicht bietet, eignet er sich nicht für einen Release-Build, vorausgesetzt, der Release-Build wird in der Produktion bereitgestellt.

Jetzt können Sie fragen, warum es in der C-Standardbibliothek keine solche Funktion oder keinen solchen Mechanismus gibt, der diese Art von Flexibilität bietet. Tatsächlich gibt es in C ++ einen Standardmechanismus mit all diesen (und weiteren) Funktionen, und Sie wissen, dass dies Ausnahmen genannt werden .

In C ist es jedoch schwierig, einen guten Allzweck-Standardmechanismus für die Fehlerbehandlung mit allen genannten Funktionen zu implementieren, da es keine Ausnahmen als Teil der Programmiersprache gibt. Daher haben die meisten C-Programme ihre eigenen Fehlerbehandlungsmechanismen mit Rückkehrcodes oder "goto" s oder "long jumps" oder einer Mischung daraus. Dies sind oft pragmatische Lösungen, die für die jeweilige Art von Programm geeignet sind, aber nicht "allgemein genug" sind, um in die C-Standardbibliothek zu passen.

Doc Brown
quelle
Ah, ich habe das C-Erbe total vergessen #include <cassert>. Das macht jetzt total Sinn.
Niemand
1
Ich stimme dieser ganzen Antwort überhaupt nicht zu. Das Auslösen einer Ausnahme, wenn Ihre Anwendung festgestellt hat, dass der Quellcode fehlerhaft war, ist der letzte Ausweg in Sprachen wie Java, die es nicht besser machen können. In C ++ beenden Sie das Programm in einer solchen Situation jedoch auf jeden Fall sofort. Sie wollen nicht Stapel verursachen Abwickeln keine weiteren Operationen an dieser Stelle ausgeführt werden. Ein fehlerhaftes Programm kann möglicherweise beschädigte Daten in Dateien, Datenbanken oder Netzwerk-Sockets schreiben. Einfach so schnell wie möglich abstürzen (und möglicherweise den Prozess durch einen externen Mechanismus neu starten lassen).
Christian Hackl
1
@ChristianHackl: Ich bin einverstanden, dass es Situationen gibt, in denen das Beenden eines Programms die einzig gangbare Option ist, aber dies sollte auch im Release-Modus geschehen, und dies assertist auch das falsche Tool (das Auslösen einer Ausnahme kann in der Tat auch die falsche Reaktion sein) ). Ich bin mir jedoch ziemlich sicher, dass in der Realität assertauch viele Tests in C durchgeführt wurden, bei denen in C ++ heutzutage Ausnahmen verwendet wurden.
Doc Brown
15

Wenn Sie wünschen, dass in einer Version Asserts aktiviert wurden, haben Sie Asserts aufgefordert, den falschen Job auszuführen.

Der Sinn von Behauptungen ist, dass sie in einer Version nicht aktiviert sind. Dies ermöglicht das Testen von Invarianten während der Entwicklung mit Code, der sonst Gerüstcode sein müsste. Code, der vor der Veröffentlichung entfernt werden muss.

Wenn Sie etwas haben, von dem Sie glauben, dass es auch während der Veröffentlichung getestet werden sollte, schreiben Sie Code, der es testet. Das If throwKonstrukt funktioniert sehr gut. Wenn Sie etwas anderes sagen möchten als die anderen Würfe, verwenden Sie einfach eine beschreibende Ausnahme, die sagt, was Sie sagen möchten.

Es ist nicht so, dass Sie nicht ändern können, wie Sie Behauptungen verwenden. Wenn Sie dies tun, erhalten Sie nichts Nützliches, es läuft den Erwartungen zuwider und es bleibt Ihnen keine saubere Möglichkeit, das zu tun, was Behauptungen tun sollten. Fügen Sie Tests hinzu, die in einer Version inaktiv sind.

Ich spreche nicht von einer spezifischen Implementierung von Assert, sondern vom Konzept einer Assertion. Ich möchte nicht behaupten oder Leser verwirren. Ich wollte fragen, warum das überhaupt so ist. Warum gibt es keinen zusätzlichen release_assert? Gibt es keine Notwendigkeit dafür? Was ist der Grund für die Behauptung, dass die Freigabe deaktiviert ist? - Niemand

Warum kein relase_assert? Ehrlich gesagt, weil Asserts für die Produktion nicht gut genug sind. Ja, es gibt ein Bedürfnis, aber nichts erfüllt dieses Bedürfnis. Oh sicher, Sie können Ihre eigenen entwerfen. Mechanisch benötigt Ihre throwIf- Funktion nur einen Bool und eine Ausnahme zum Werfen. Und das passt vielleicht zu Ihren Bedürfnissen. Aber Sie schränken das Design wirklich ein. Deshalb wundert es mich nicht, dass es in Ihrer Sprachbibliothek kein integriertes System zum Werfen von Ausnahmen gibt. Es ist sicher nicht so, dass du es nicht kannst. Andere haben . Aber der Fall, dass etwas schief geht, macht für die meisten Programme 80% der Arbeit aus. Und bisher hat uns noch niemand eine gute Einheitsgröße gezeigt. Der effektive Umgang mit diesen Fällen kann kompliziert werden. Wenn wir ein Release_assert-System in Dosen gehabt hätten, das unseren Anforderungen nicht entsprochen hätte, hätte es mehr Schaden als Nutzen gebracht. Sie fragen nach einer guten Abstraktion, die bedeutet, dass Sie nicht über dieses Problem nachdenken müssen. Ich möchte auch eine, aber es sieht nicht so aus, als wären wir schon da.

Warum sind Asserts in der Version deaktiviert? Asserts wurden auf der Höhe des Alters des Gerüstcodes erstellt. Code, den wir entfernen mussten, weil wir wussten, dass wir ihn nicht in der Produktion haben wollten, aber wussten, dass wir in der Entwicklung laufen wollten, um Fehler zu finden. Asserts waren eine sauberere Alternative zu dem if (DEBUG)Muster, bei dem wir den Code belassen , ihn aber deaktivieren konnten. Dies geschah, bevor Unit-Tests als Hauptmethode zur Trennung von Testcode und Produktionscode eingesetzt wurden. Asserts werden auch heute noch von erfahrenen Unit-Testern verwendet, um die Erwartungen zu verdeutlichen und um Fälle abzudecken, in denen sie immer noch besser abschneiden als Unit-Tests.

Warum nicht einfach den Debugging-Code in der Produktion belassen? Da der Produktionscode das Unternehmen nicht in Verlegenheit bringen, die Festplatte nicht formatieren, die Datenbank nicht beschädigen und dem Präsidenten keine drohenden E-Mails senden darf. Kurz gesagt, es ist schön, Debugging-Code an einem sicheren Ort schreiben zu können, an dem Sie sich darüber keine Gedanken machen müssen.

kandierte_orange
quelle
2
Ich denke meine Frage ist: Warum muss dieser Code vor der Veröffentlichung entfernt werden? Die Überprüfungen sind kein großer Leistungsverlust, und wenn sie fehlschlagen, gibt es definitiv ein Problem, bei dem ich eine direktere Fehlermeldung vorziehen würde. Welchen Vorteil bietet mir die Verwendung einer Ausnahme anstelle einer Behauptung? Ich würde wahrscheinlich nicht wollen, dass nicht verwandter Code dazu in der Lage catch(...)ist.
Niemand
2
@Nobody Der größte Vorteil, Asserts nicht für Nicht-Assert-Anforderungen zu verwenden, ist, dass Sie Ihre Leser nicht verwirren. Wenn Sie nicht möchten, dass der Code deaktiviert wird, verwenden Sie keine Redewendungen, die dies signalisieren. Es ist so schlimm, als würde man if (DEBUG)etwas anderes als das Debuggen von Code steuern. Die Mikrooptimierung kann in Ihrem Fall nichts bedeuten. Aber die Redewendung sollte nicht unterlaufen werden, nur weil Sie sie nicht brauchen.
candied_orange
Ich glaube, ich habe meine Absicht nicht klar genug ausgedrückt. Ich spreche nicht von einer konkreten Umsetzung, assertsondern vom Konzept einer Behauptung. Ich möchte assertdie Leser nicht missbrauchen oder verwirren. Ich wollte fragen, warum das überhaupt so ist. Warum gibt es keine zusätzlichen release_assert? Gibt es keine Notwendigkeit dafür? Was ist der Grund für die Behauptung, dass die Freigabe deaktiviert ist?
Niemand
2
You're asking for a good abstraction.Ich bin mir darüber nicht sicher. Ich möchte mich hauptsächlich mit Problemen befassen, bei denen es keine Wiederherstellung gibt und die niemals auftreten sollten. Nehmen Sie dies zusammen mit Because production code needs to [...] not format the hard drive [...]und ich würde sagen, für Fälle, in denen Invarianten gebrochen sind, würde ich jederzeit eine Freigabe über UB annehmen.
Niemand
1
@Nobody "Probleme, bei denen keine Wiederherstellung erfolgt" können behoben werden, indem Ausnahmen ausgelöst und nicht abgefangen werden. Sie können feststellen, dass sie im Meldungstext der Ausnahme niemals vorkommen sollten.
candied_orange
4

Behauptungen sind ein Debugging-Tool , keine defensive Programmiertechnik. Wenn Sie die Validierung unter allen Umständen durchführen möchten, führen Sie die Validierung durch, indem Sie eine Bedingung schreiben - oder erstellen Sie ein eigenes Makro, um die Boilerplate zu verkleinern.

amon
quelle
4

assertist eine Form der Dokumentation, wie Kommentare. Wie Kommentare würden Sie sie normalerweise nicht an Kunden versenden - sie gehören nicht in den Versionscode.

Das Problem bei Kommentaren ist jedoch, dass sie veraltet sein können und dennoch unverändert bleiben. Aus diesem Grund sind Asserts gut - sie werden im Debug-Modus überprüft. Wenn die Zusicherung veraltet ist, stellen Sie sie schnell fest und wissen immer noch, wie die Zusicherung behoben werden kann. Dieser Kommentar, der vor 3 Jahren veraltet war? Irgendjemand schätzt.

MSalters
quelle
1
Ein Abbruch einer fehlgeschlagenen Invariante, bevor etwas Schlimmes passiert, ist also kein gültiger Anwendungsfall?
Niemand
3

Wenn Sie nicht möchten, dass eine "Zusicherung" deaktiviert wird, können Sie eine einfache Funktion schreiben, die einen ähnlichen Effekt hat:

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

Das heißt, assertist für Tests , bei denen Sie tun wollen , dass sie in dem mitgelieferten Produkt gehen weg. Wenn Sie möchten, dass dieser Test Teil des definierten Verhaltens des Programms ist, assertist das das falsche Werkzeug.

Nicol Bolas
quelle
1
Ich bin mir dessen sehr wohl bewusst. Die Frage ist eher, warum der Standard so ist, wie er ist.
Niemand
3

Es ist sinnlos zu diskutieren, was behauptet werden soll, was getan wird. Wenn Sie etwas anderes wollen, schreiben Sie Ihre eigene Funktion. Ich habe zum Beispiel Assert, der im Debugger stoppt oder nichts tut. Ich habe AssertFatal, das die App zum Absturz bringt. Ich habe die Bool-Funktionen Asserted und AssertionFailed, die das Ergebnis bestätigen und zurückgeben, damit ich die Situation sowohl bestätigen als auch behandeln kann.

Bei unerwarteten Problemen müssen Sie entscheiden, wie Entwickler und Benutzer am besten damit umgehen können.

gnasher729
quelle
Ich wollte nicht darüber streiten, was die Behauptung tun soll. Meine Motivation für diese Frage war, die Gründe für Behauptungen zu finden, die nicht in der Release-Version enthalten sind, da ich im Begriff bin, meine eigenen zu schreiben und Informationen über Anwendungsfälle, Erwartungen und Fallen zu sammeln.
Niemand
Hm, wäre das nicht verify(cond)der normale Weg, das Ergebnis durchzusetzen und zurückzugeben?
Deduplikator
1
Ich programmiere in Ruby, nicht in C, aber ich bin mit den meisten Antworten oben nicht einverstanden, da das Anhalten des Programms mit dem Drucken eines Backtraces für mich als defensive Programmiertechnik in Ordnung ist ....... mein Kommentar passte nicht zum Maximale Kommentargröße in Zeichen, daher habe ich unten eine weitere Antwort geschrieben.
Nakilon
2

Wie andere betonten, assertist es eine Art letzte Bastion der Verteidigung gegen Programmiererfehler, die niemals passieren sollten. Es handelt sich um Plausibilitätsprüfungen, die hoffentlich zum Zeitpunkt Ihres Versands nicht von links nach rechts scheitern sollten.

Es wurde auch so konzipiert , dass es in stabilen Release-Builds nicht verwendet wird, aus welchen Gründen auch immer die Entwickler dies für nützlich halten: Ästhetik, Leistung, was immer sie wollen. Es ist Teil dessen, was einen Debugbuild von einem Releasebuild trennt, und per Definition enthält ein Releasebuild keine derartigen Aussagen. Es gibt also eine Subversion des Designs, wenn Sie den analogen "Release-Build mit vorhandenen Zusicherungen" freigeben möchten , bei dem es sich um einen Versuch handelt, ein Release mit einer _DEBUGPräprozessordefinition und keiner NDEBUGDefinition zu erstellen . Es ist nicht mehr wirklich ein Release Build.

Das Design erstreckt sich sogar bis in die Standardbibliothek. Als sehr einfaches Beispiel unter vielen, viele Implementierungen std::vector::operator[]wird asserteine Plausibilitätsprüfung , um sicherzustellen , überprüfen Sie nicht mit dem Vektor außerhalb der Grenzen. Und die Standardbibliothek wird deutlich schlechter, wenn Sie solche Prüfungen in einem Release-Build aktivieren. Ein Maßstab für die vectorVerwendungoperator[]und ein Füllcode mit solchen Zusicherungen, die für ein einfaches altes dynamisches Array enthalten sind, zeigt häufig, dass das dynamische Array erheblich schneller ist, bis Sie solche Überprüfungen deaktivieren, sodass sie sich häufig auf die Leistung auswirken. Eine Nullzeigerprüfung hier und eine Prüfung außerhalb der Grenzen dort kann tatsächlich einen enormen Aufwand bedeuten, wenn solche Prüfungen millionenfach auf jeden Frame in kritischen Schleifen angewendet werden, die dem Code so einfach wie das Dereferenzieren eines intelligenten Zeigers oder das Zugreifen auf ein Array vorangehen.

Sie wünschen sich daher höchstwahrscheinlich ein anderes Tool für den Job und eines, das nicht für Release-Builds vorgesehen ist, wenn Sie Release-Builds wünschen, die solche Überprüfungen in Schlüsselbereichen durchführen. Das nützlichste, was ich persönlich finde, ist die Protokollierung. Wenn ein Benutzer in diesem Fall einen Fehler meldet, wird es viel einfacher, wenn er ein Protokoll anfügt und die letzte Zeile des Protokolls einen großen Hinweis darauf gibt, wo der Fehler aufgetreten ist und was er sein könnte. Wenn ich dann ihre Schritte in einem Debugbuild wiedergebe, erhalte ich möglicherweise ebenfalls einen Assertionsfehler, und dieser Assertionsfehler gibt mir weitere wichtige Hinweise, um meine Zeit zu optimieren. Da die Protokollierung jedoch relativ teuer ist, verwende ich sie nicht, um äußerst einfache Sicherheitsüberprüfungen durchzuführen, z. B. um sicherzustellen, dass in einer generischen Datenstruktur nicht außerhalb der Grenzen auf ein Array zugegriffen wird.

Schließlich, und einigermaßen in Übereinstimmung mit Ihnen, könnte ein vernünftiger Fall eintreten, in dem Sie Testern tatsächlich etwas übergeben möchten, das einem Debugbuild beim Alphatesten ähnelt, beispielsweise mit einer kleinen Gruppe von Alphatestern, die beispielsweise einen NDA unterzeichnet haben . Dort wird der Alphatest möglicherweise optimiert, wenn Sie Ihren Testern nicht nur einen vollständigen Release-Build mit Debugging-Informationen übergeben, sondern auch einige Debug- / Entwicklungsfunktionen wie Tests, die sie ausführen können, und ausführlichere Ausgaben, während sie die Software ausführen. Ich habe zumindest einige große Spielefirmen gesehen, die so etwas für Alpha gemacht haben. Aber das ist für so etwas wie Alpha oder interne Tests, bei denen Sie wirklich versuchen, den Testern etwas anderes als einen Release-Build zu geben. Wenn Sie tatsächlich versuchen, einen Release-Build zu versenden, sollte dies per Definition nicht der Fall sein_DEBUG definiert oder das ist wirklich verwirrend den Unterschied zwischen einem "Debug" und "Release" Build.

Warum muss dieser Code vor der Veröffentlichung entfernt werden? Die Überprüfungen sind kein großer Leistungsverlust, und wenn sie fehlschlagen, gibt es definitiv ein Problem, bei dem ich eine direktere Fehlermeldung vorziehen würde.

Wie oben ausgeführt, sind die Überprüfungen vom Standpunkt der Leistung aus nicht unbedingt trivial. Viele sind wahrscheinlich trivial, aber selbst die Standardbibliothek verwendet sie und kann die Leistung in vielen Fällen auf inakzeptable Weise beeinträchtigen, wenn beispielsweise das Durchlaufen von Direktzugriffen std::vectorin einem angeblich optimierten Release-Build viermal so lange gedauert hat Aufgrund seiner Grenzen sollte die Überprüfung niemals scheitern.

In einem früheren Team mussten wir in unserer Matrix- und Vektorbibliothek einige Asserts in bestimmten kritischen Pfaden ausschließen, um Debugbuilds schneller auszuführen, da diese Asserts die mathematischen Operationen um eine Größenordnung bis zu dem Punkt verlangsamten, an dem sie ausgeführt wurden Ab sofort müssen wir 15 Minuten warten, bevor wir den Code von Interesse nachvollziehen können. Meine Kollegen wollten eigentlich nur die entfernenassertsdirekt, weil sie fanden, dass gerade das einen gewaltigen Unterschied ausmachte. Stattdessen haben wir uns darauf festgelegt, dass kritische Debug-Pfade diese vermeiden. Wenn diese kritischen Pfade die Vektor- / Matrixdaten direkt verwenden, ohne die Begrenzungsprüfung zu durchlaufen, wurde die für die Durchführung der vollständigen Operation (die mehr als nur Vektor- / Matrixmathematik umfasste) erforderliche Zeit von Minuten auf Sekunden reduziert. Das ist also ein extremer Fall, aber die Aussagen sind definitiv nicht immer vernachlässigbar, auch nicht in der Nähe.

Aber auch so ist es halt so assertsgestaltet. Wenn sie nicht auf ganzer Linie eine so große Auswirkung auf die Leistung hatten, würde ich es bevorzugen, wenn sie mehr als eine Debug-Build-Funktion wären, oder wir könnten eine Funktion verwenden, vector::atdie die Überprüfung der Grenzen auch bei Release-Builds und Throws on out-of-bounds einschließt Zugriff, zB (noch mit einem riesigen Performance-Hit). Momentan finde ich ihr Design aufgrund der enormen Leistungseinbußen in meinen Fällen viel nützlicher als ein Feature, das nur für das Debuggen erstellt wird und bei der NDEBUGDefinition weggelassen wird. Für die Fälle, mit denen ich zumindest gearbeitet habe, ist es ein großer Unterschied, dass bei einem Release-Build keine Überprüfung der Integrität durchgeführt wird, die eigentlich niemals scheitern sollte.

vector::at gegen vector::operator[]

Ich denke, dass die Unterscheidung dieser beiden Methoden sowohl den Kern als auch die Alternative ausmacht: Ausnahmen. vector::operator[]Implementierungen, die normalerweise assertsicherstellen, dass der Zugriff außerhalb der Grenzen einen leicht reproduzierbaren Fehler auslöst, wenn versucht wird, auf einen Vektor außerhalb der Grenzen zuzugreifen. Die Bibliotheksimplementierer gehen jedoch davon aus, dass es in einem optimierten Release-Build keinen Cent kostet.

In der Zwischenzeit vector::atwird bereitgestellt, was selbst in Release-Builds immer die Grenzen überschreitet, aber es hat eine Leistungsbeeinträchtigung bis zu dem Punkt, an dem ich oft viel mehr Code sehe, vector::operator[]als verwendet vector::at. Ein Großteil des Designs von C ++ spiegelt die Idee wider, "für das zu zahlen, was Sie verwenden / benötigen", und viele Leute bevorzugen es oft operator[], was sich nicht einmal mit den Einschränkungen für das Einchecken von Release-Builds auf der Grundlage der Vorstellung, dass sie nichts tun, beschäftigt Es ist nicht erforderlich, dass die Grenzen in ihren optimierten Release-Builds überprüft werden. Wenn Assertions in Release-Builds aktiviert würden, wäre die Leistung dieser beiden Assertions plötzlich identisch und die Verwendung von Vector wäre immer langsamer als ein dynamisches Array. Ein großer Teil des Designs und des Nutzens von Assertions basiert auf der Idee, dass sie in einem Release-Build frei werden.

release_assert

Dies ist interessant, nachdem Sie diese Absichten entdeckt haben. Natürlich wären die Anwendungsfälle für jeden anders, aber ich denke, ich würde eine Verwendung für einen finden, release_assertder die Prüfung durchführt und die Software abstürzt, die eine Zeilennummer und eine Fehlermeldung anzeigt, sogar in Release-Builds.

Es wäre für einige obskure Fälle in meinem Fall, in denen ich nicht möchte, dass die Software ordnungsgemäß wiederhergestellt wird, wie es der Fall wäre, wenn eine Ausnahme ausgelöst wird. Ich möchte, dass es in solchen Fällen auch bei der Freigabe abstürzt, damit der Benutzer eine Zeilennummer erhält, um zu melden, wenn die Software auf etwas stößt, das niemals passieren sollte, und zwar immer noch im Bereich der Überprüfung auf Programmiererfehler, nicht auf externe Eingabefehler wie Ausnahmen, aber billig genug, um ohne Rücksicht auf die Kosten für die Veröffentlichung fertig zu werden.

Es gibt tatsächlich einige Fälle , in denen ich einen harten Absturz mit einer Zeilennummer und Fehlermeldung finden würde bevorzugt zu anmutig erholt sich von einer geworfenen Ausnahme , die billig genug , um in einem Release zu halten sein könnte. In einigen Fällen ist es nicht möglich, eine Ausnahme zu beheben, z. B. beim Versuch, eine vorhandene Ausnahme zu beheben. Dort würde ich eine perfekte Passform für eine finden, release_assert(!"This should never, ever happen! The software failed to fail!");und das wäre natürlich spottbillig, da die Prüfung in erster Linie auf einem außergewöhnlichen Pfad durchgeführt würde und auf normalen Ausführungspfaden nichts kosten würde.


quelle
Ich wollte keine Zusicherungen im Code von Drittanbietern aktivieren, siehe die klarstellende Bearbeitung. Das Zitat aus meinem Kommentar lässt den Kontext aus meiner Frage unberücksichtigt: Es geht Additionally, the performance argument for me only counts when it is a measurable problem.also darum, von Fall zu Fall zu entscheiden, wann eine Behauptung aus der Veröffentlichung genommen werden soll.
Niemand
Eines der Probleme mit dem letzten Kommentar ist, dass die Standardbibliothek dasselbe verwendet, assertdas auf denselben _DEBUG/NDEBUGPräprozessordefekten beruht, die Sie bei der Verwendung des assertMakros verwenden würden. Es gibt also keine Möglichkeit, Zusicherungen selektiv nur für einen Codeteil zu aktivieren, ohne sie auch für die Standardbibliothek zu aktivieren, z. B. vorausgesetzt, sie verwendet die Standardbibliothek.
Es gibt eine STL-Iteratorprüfung, die Sie separat deaktivieren können, wodurch einige, aber nicht alle Behauptungen deaktiviert werden.
Grundsätzlich handelt es sich um ein grobes Tool, nicht um ein granulares Tool, das immer in der Absicht entwickelt wurde, in Releases deaktiviert zu werden, sodass Ihre Teamkollegen es möglicherweise in kritischen Bereichen einsetzen, obwohl davon ausgegangen wird, dass es die Release-Leistung nicht beeinträchtigt. Dies wird von der Standardbibliothek verwendet In kritischen Bereichen wird es von anderen Drittanbieter-Bibliotheken auf diese Weise verwendet. Und wenn Sie etwas möchten, das Sie spezifischer für einen Teil des Codes und nicht für den anderen aktivieren / deaktivieren können, kehren wir zu einem anderen Aspekt als zurück assert.
vector::operator[]und vector::atwürde zum Beispiel praktisch die gleiche Leistung haben, wenn Assertions in Release-Builds aktiviert wären, und es wäre operator[]vom Leistungsstandpunkt aus nicht mehr sinnvoll, sie zu verwenden, da sie ohnehin die Grenzüberprüfung durchführen würde.
2

Ich programmiere in Ruby, nicht in C / C ++, und spreche daher nicht über den Unterschied zwischen Zusicherungen und Ausnahmen, sondern möchte darüber als eine Sache sprechen , die die Laufzeit stoppt . Ich bin mit den meisten Antworten oben nicht einverstanden, da das Anhalten des Programms mit dem Drucken eines Backtraces für mich als defensive Programmiertechnik gut funktioniert .
Wenn es eine Möglichkeit gibt, die Assert-Routine aufzurufen (ganz gleich, wie sie syntaktisch geschrieben ist und ob das Wort "assert" verwendet wird oder in der Programmiersprache oder in dsl überhaupt vorhanden ist), bedeutet dies, dass einige Arbeiten durchgeführt werden müssen und das Produkt geht sofort von "freigegeben, einsatzbereit" zurück zu "Needs Patch" - entweder umschreiben auf echte Ausnahmebehandlung oder einen Fehler beheben, der dazu führte, dass falsche Daten angezeigt wurden.

Ich meine, Assert ist keine Sache, mit der man leben und häufig telefonieren sollte - es ist ein Stoppsignal, das anzeigt, dass man etwas tun sollte, damit es nie wieder vorkommt. Und zu sagen, dass "Release Build sollte keine Asserts haben" ist wie zu sagen, dass "Release Build sollte keine Bugs haben" - Alter, es ist fast unmöglich, damit umzugehen.
Oder denken Sie an sie als an "Fehlgeschlagene Unittests, die vom Endbenutzer erstellt und ausgeführt werden". Sie können nicht vorhersagen, was der Benutzer mit Ihrem Programm machen wird, aber wenn etwas zu ernstes schief geht, sollte es anhalten - ähnlich wie beim Erstellen von Pipelines - Sie halten den Prozess an und veröffentlichen nicht, oder? ? Die Behauptung zwingt den Benutzer, anzuhalten, zu melden und auf Ihre Hilfe zu warten.

Nakilon
quelle