Wie andere betonten, assert
ist 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 _DEBUG
Präprozessordefinition und keiner NDEBUG
Definition 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 assert
eine 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 vector
Verwendungoperator[]
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::vector
in 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 entfernenasserts
direkt, 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 asserts
gestaltet. 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::at
die 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 NDEBUG
Definition 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 assert
sicherstellen, 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::at
wird 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_assert
der 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.