Wie wichtig ist Ihnen die Ausnahmesicherheit in Ihrem C ++ - Code?


19

Jedes Mal, wenn ich in Betracht ziehe, meinen Code ausnahmesicher zu machen, rechtfertige ich dies, weil es so zeitaufwendig wäre. Betrachten Sie dieses relativ einfache Snippet:

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

Um eine grundlegende Ausnahmegarantie zu implementieren, könnte ich einen Bereichszeiger für die newAufrufe verwenden. Dies würde Speicherverluste verhindern, wenn einer der Aufrufe eine Ausnahme auslösen würde.

Nehmen wir jedoch an, ich möchte eine starke Ausnahmegarantie implementieren. Zumindest müsste ich einen gemeinsamen Zeiger für meine Container implementieren (ich verwende Boost nicht), einen Nothrow Entity::Swapfür das atomare Hinzufügen der Komponenten und eine Art Idiom für das atomare Hinzufügen von sowohl dem Vectorals auch Map. Diese wären nicht nur zeitaufwändig in der Implementierung, sondern auch teuer, da sie viel mehr Kopien erfordern als die unsichere Ausnahmelösung.

Letztendlich fühlt es sich für mich so an, als wäre die Zeit, die ich damit verbringe, nicht gerechtfertigt, nur damit eine einfache CreateEntityFunktion absolut ausnahmesicher ist. Ich möchte wahrscheinlich nur, dass das Spiel einen Fehler anzeigt und an diesem Punkt trotzdem geschlossen wird.

Wie weit gehen Sie in Ihren eigenen Spielprojekten? Ist es im Allgemeinen akzeptabel, unsicheren Ausnahmecode für ein Programm zu schreiben, das bei einer Ausnahme einfach abstürzen kann?


21
Als ich in der Vergangenheit an C ++ - Projekten gearbeitet habe, haben wir im Grunde so getan, als gäbe es keine Ausnahmen.
Tetrad

2
Ich persönlich liebe Ausnahmen und genieße es oft, herauszufinden, wie mein Code starke Garantien bietet. Wenn du nur abstürzen willst, wenn du eine Ausnahme bekommst ... kümmere dich vielleicht nicht darum.

7
Ich persönlich verwende Ausnahmen, um ... hm ... "Ausnahmen" zu verwalten, keine Fehler. Wenn ich weiß, dass eine Funktion abstürzen kann, lasse ich sie abstürzen, aber wenn ich weiß, dass eine Funktion ein Archiv möglicherweise nicht erfolgreich liest, aber es gibt einen anderen Weg, ich umgebe es mit einem Versuch, zu fangen. und wenn es eine "nicht lesbare" Ausnahme ist, verwalte ich es einfach anders, als ich es ohne das Lesen des Archivs tun würde.
Gustavo Maciel

Was Gtoknu gesagt hat, müssen Sie sich bewusst sein, welche Ausnahmen externe Bibliotheken auslösen können.
Peter Ølsted

Antworten:


26

Wenn Sie Ausnahmen verwenden möchten (was nicht heißt, dass Sie sollten, es gibt Vor- und Nachteile für diesen Ansatz, die außerhalb des spezifischen Bereichs dieser Frage liegen), sollten Sie ordnungsgemäß ausnahmesicheren Code schreiben.

Code, der keine Ausnahmen verwendet und ausdrücklich nicht ausnahmesicher ist, ist besser als Code, der sie verwendet, aber die Ausnahmesicherheit halbwegs sicher macht. In letzterem Fall gibt es eine ganze Reihe von Fehlern und Fehlfunktionen, die auftreten können, wenn eine Ausnahme auftritt, die Sie wahrscheinlich gar nicht beheben können, sodass einer der Vorteile von Ausnahmen gänzlich null und nichtig ist. Auch wenn es eine relativ seltene Klasse von Fehlern ist, ist dies dennoch möglich.

Dies soll nicht heißen, dass bei der Verwendung von Ausnahmen jeder Code die starke Ausnahmegarantie bieten muss - nur, dass jedes Codeteil eine Garantie (keine, einfach, stark) bieten sollte, damit bei der Verwendung dieses Codes Sie wissen, welche Garantien Ihnen der Verbraucher bieten kann. Im Allgemeinen sollte die Garantie umso höher sein, je niedriger die betreffende Komponente ist.

Wenn Sie niemals eine starke Garantie geben oder die Paradigmen für die Ausnahmebehandlung und die damit verbundenen zusätzlichen Arbeiten und Nachteile nicht wirklich akzeptieren, werden Sie auch nicht alle Vorteile erhalten, die sich daraus ergeben, und es könnte für Sie einfacher sein Zeit, nur auf Ausnahmen zu verzichten.


12
Dies allein ist eine +1 wert: "Code, der keine Ausnahmen verwendet und ausdrücklich nicht ausnahmesicher ist, ist besser als Code, der sie verwendet, aber die Ausnahmesicherheit halbwegs sicher macht."
Maximus Minimus

14

Meine allgemeine Faustregel: Wenn Sie Ausnahmen verwenden müssen, verwenden Sie Ausnahmen. Wenn Sie keine Ausnahmen verwenden müssen, verwenden Sie keine Ausnahmen. Wenn Sie nicht wissen, ob Sie Ausnahmen verwenden müssen, müssen Sie keine Ausnahmen verwenden.

Ausnahmen bilden die letzte Verteidigungslinie in immer aktiven unternehmenskritischen Anwendungen. Verwenden Sie Ausnahmen, wenn Sie eine Flugsicherungssoftware schreiben, die niemals versagen kann. Wenn Sie einen Steuercode für ein Kernkraftwerk schreiben, verwenden Sie Ausnahmen.

Bei der Entwicklung eines Spiels ist es hingegen viel besser, dem Mantra zu folgen: Früh abstürzen, laut abstürzen. Wenn es ein Problem gibt, möchten Sie es so schnell wie möglich erfahren. Sie möchten nicht, dass Fehler unsichtbar „behandelt“ werden, da dies häufig die eigentliche Ursache für späteres Fehlverhalten verschleiert und das Debuggen erheblich erschwert.


5

Das Problem mit Ausnahmen ist, dass Sie sich sowieso mit ihnen befassen müssen. Wenn Sie eine Ausnahme erhalten, weil Ihnen der Speicher nicht ausreicht, ist es möglich, dass Sie diesen Fehler ohnehin nicht behandeln können. Könnte auch einfach das gesamte Spiel in einem riesigen Ausnahmehandler ablegen, der eine Fehlerbox auftaucht.

Für die Spieleentwicklung versuche ich lieber, Wege zu finden, wie ich meinen Code so gut wie möglich mit Fehlern fortsetzen kann.

Zum Beispiel werde ich ein spezielles ERROR-Mesh in mein Spiel einbauen. Es ist ein roter Kreis mit einem weißen X und einem weißen Rand. Je nach Spiel ist ein unsichtbarer besser. Es hat eine Singleton-Instanz, die beim Start initialisiert wird. Da es in den Code eingebettet ist und keine externen Dateien verwendet, sollte es nicht möglich sein, Fehler zu verursachen.

Falls ein normaler loadMesh-Aufruf fehlschlägt, anstatt einen Fehler zurück zu werfen und auf den Desktop abzustürzen, wird der Fehler unbemerkt in der Konsole / Protokolldatei protokolliert und das fest codierte Fehlernetz zurückgegeben. Ich fand heraus, dass Skyrim tatsächlich dasselbe tut, wenn ein Mod Dinge vermasselt. Es besteht die gute Möglichkeit, dass der Spieler das Fehlernetz nicht einmal sieht (es sei denn, es gibt ein schwerwiegendes Problem, und viele davon sind ähnlich).

Es gibt auch einen Fallback-Shader. Eine, die grundlegende Vertex-Matrix-Operationen ausführt, aber wenn es aus irgendeinem Grund keine einheitliche Matrix gibt, sollte sie trotzdem die orthogonale Ansicht ausführen. Es hat keine ordnungsgemäße Schattierung / Beleuchtung / Materialien (obwohl es ein color_overide-Flag hat, damit ich es mit meinem Fehlernetz verwenden kann).

Das einzig mögliche Problem ist, ob die fehlerhafte Mesh-Version das Spiel auf irgendeine Weise dauerhaft unterbricht. Stellen Sie zum Beispiel sicher, dass es keine Physik anwendet, da, wenn der Spieler ein Gebiet lädt, das fehlerhafte Netz auf den Boden fallen kann. Wenn das Spiel diesmal neu geladen wird und das richtige Netz funktioniert, ist es möglicherweise größer eingebettet in den Boden. Das sollte nicht zu schwierig sein, da Sie wahrscheinlich Ihre physikalischen Netze mit Ihren grafischen speichern werden. Scripting kann auch ein Problem sein. Mit ein paar Sachen musst du nur schwer sterben. Ich bin mir nicht wirklich sicher, was ein Fallback-Level bedeuten würde (obwohl Sie einen kleinen Raum mit einem Schild erstellen könnten, das besagt, dass ein Fehler aufgetreten ist, stellen Sie sicher, dass das Speichern deaktiviert ist, da Sie nicht möchten, dass der Spieler darin steckt).

Im Fall von 'new' ist es für die Spieleentwicklung möglicherweise besser, eine Fabrik zu verwenden. Es kann Ihnen verschiedene andere Vorteile bieten, z. B. die Vorbelegung von Objekten, bevor Sie sie tatsächlich benötigen, und / oder die Massenproduktion. Dies erleichtert auch die Erstellung eines globalen Index von Objekten, die Sie für die Speicherverwaltung verwenden können, indem Sie nach einer ID suchen (obwohl dies auch in Konstruktoren möglich ist). Und natürlich können Sie auch dort mit Zuordnungsfehlern umgehen.


2

Das große Problem bei fehlenden Laufzeitprüfungen und Abstürzen besteht darin, dass ein Hacker einen Absturz verwenden kann, um den Prozess und möglicherweise anschließend die Maschine zu übernehmen.

Jetzt kann ein Hacker einen tatsächlichen Absturz nicht ausnutzen, sobald der Absturz eintritt, ist der Prozess nutzlos und harmlos. Wenn Sie einfach von einem Nullzeiger lesen, ist das ein sauberer Absturz, Sie machen einen Fehler und der Prozess stirbt sofort.

Die Gefahr besteht, wenn Sie einen Befehl ausführen, der technisch nicht illegal ist, aber nicht Ihrer Absicht entspricht. Dann kann ein Hacker diese Aktion möglicherweise mithilfe sorgfältig erstellter Daten steuern, die ansonsten sicher sein sollten.

Eine nicht abgefangene Ausnahme ist eigentlich kein Absturz, sondern nur eine Möglichkeit, das Programm zu beenden. Ausnahmen sind nur möglich, weil jemand eine Bibliothek geschrieben hat, die unter bestimmten Umständen eine Ausnahme auslöst. In der Regel, um nicht in eine unerwartete Situation zu geraten, die für einen Hacker eine Chance darstellt.

Tatsächlich besteht der gefährliche Zug darin, Ausnahmen zu fangen, anstatt sie ausrutschen zu lassen. Das heißt nicht, dass Sie dies niemals tun sollten, sondern dass Sie nur diejenigen fangen, von denen Sie wissen, dass Sie damit umgehen können, und den Rest ausrutschen lassen. Andernfalls riskieren Sie, die Sicherheitsmaßnahme, die Sie sorgfältig in Ihre Bibliothek aufgenommen haben, rückgängig zu machen.

Konzentrieren Sie sich auf die Fehler, die nicht unbedingt Ausnahmen auslösen, z. B. das Schreiben über das Ende eines Arrays hinaus. Ihr Programm würde das nicht zufällig können?


2

Sie müssen sich Ihre Ausnahmen und die Bedingungen ansehen, die sie auslösen können. Verdammt viel Zeit, ein großer Prozentsatz dessen, wofür Menschen Ausnahmen verwenden, kann durch ordnungsgemäße Überprüfungen während des Entwicklungs- / Debug-Build- / Testzyklus vermieden werden. Verwenden Sie Asserts, übergeben Sie Referenzen, initialisieren Sie lokale Variablen bei der Deklaration immer, setzen Sie alle Zuordnungen auf Null, setzen Sie NULL nach Null usw., und viele theoretische Ausnahmefälle verschwinden einfach.

Bedenken Sie: Wenn etwas fehlschlägt und möglicherweise eine Ausnahme auslöst - wie wirkt es sich aus? Wie wichtig ist das? Wenn Sie bei einer Speicherzuweisung in einem Spiel versagen (um das ursprüngliche Beispiel zu nehmen), haben Sie im Allgemeinen ein größeres Problem als bei der Behandlung des Fehlerfalls, und die Behandlung des Fehlerfalls mithilfe einer Ausnahme wird dieses größere Problem nicht verursachen Geh weg. Schlimmer noch - es kann tatsächlich die Tatsache verbergen, dass das größere Problem sogar da ist. Willst du das? Ich weiß ich nicht. Ich weiß, dass ich, wenn ich eine Speicherzuweisung nicht erreiche, schrecklich abstürzen und brennen möchte und dies so nah wie möglich am Fehlerort, damit ich in den Debugger einbrechen, herausfinden kann, was vor sich geht, und ihn richtig reparieren kann.


1

Ich bin nicht sicher, ob es angemessen ist, jemanden anders zu zitieren, aber ich fühle, dass keine der anderen Antworten dies wirklich berührt.

Zitiert Jason Gregory aus seinem Buch Game Engine Architecture . (TOLLES BUCH!)

Die strukturierte Ausnahmebehandlung (SEH) erhöht den Programmaufwand erheblich. Jeder Stapelrahmen muss erweitert werden, um zusätzliche Informationen zu enthalten, die für das Abwickeln des Stapels erforderlich sind. Außerdem ist das Abwickeln des Stapels normalerweise sehr langsam - in der Größenordnung von zwei bis drei Dies ist um ein Vielfaches teurer als die einfache Rückkehr von der Funktion. Wenn auch nur eine Funktion in Ihrem Programm (oder Ihrer Bibliothek) SEH verwendet, muss Ihr gesamtes Programm SEH verwenden. Der Compiler kann nicht wissen, welche Funktionen sich zu welchem ​​Zeitpunkt über Ihnen auf dem Aufrufstapel befinden Sie werfen eine Ausnahme. "

Wenn das gesagt ist, verwendet mein Motor, den ich baue, keine Ausnahmen. Dies liegt an den oben erwähnten potenziellen Leistungskosten und an der Tatsache, dass Fehlerrückgabecodes für alle meine Zwecke einwandfrei funktionieren. Das einzige Mal, dass ich wirklich nach Fehlern suchen muss, ist das Initialisieren und Laden von Ressourcen. In diesen Fällen funktioniert die Rückgabe eines Booleschen Werts, der den Erfolg anzeigt, einwandfrei.


2
Die strukturierte Ausnahmebehandlung ist eine spezielle Art der Ausnahmebehandlung, die unter Windows verfügbar ist und nicht mit C ++ - Ausnahmen im Allgemeinen identisch ist.
Kylotan

Doh, ich kann nicht glauben, dass ich das nicht wusste. Danke für die Korrektur!
KlashnikovKid

0

Ich mache meine Codeausnahme immer sicher, einfach weil es einfacher ist zu wissen, wo Probleme auftreten, da ich etwas protokollieren kann, wenn eine Ausnahme generiert oder abgefangen wird.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.