Ausnahmen in C ++ - Spiel-DLLs auslösen? Vor-und Nachteile


8

Was sind die Vor- und Nachteile der Verwendung von Ausnahmen in C ++ in Bezug auf die Spieleentwicklung ?

Laut Google Style Guide werden Ausnahmen aus verschiedenen Gründen nicht verwendet. Sind die gleichen Gründe für die Spieleentwicklung relevant?

Wir verwenden keine C ++ - Ausnahmen ... - google-styleguide.googlecode.com

Einige Fragen zum Nachdenken.

  • Wie es sich um die Entwicklung einer Bibliothek handelt, die in mehreren Projekten verwendet wird.
  • Wie wirkt es sich auf Unit-Tests, Integrationstests usw. aus?
  • Was bedeutet es, Bibliotheken von Drittanbietern zu verwenden?

3
Seltsamerweise hatte niemand Profis
David Young

Antworten:


7

Es gibt auch einige schlimme Konsequenzen für die Verwendung von Ausnahmen in C ++, wenn Sie nicht alle Details ihrer Funktionsweise kennen. Da viele Spiele in C ++ geschrieben sind, kann dies ein Faktor sein.

Beispielsweise kann das Auslösen einer Ausnahme in einem Destruktor in C ++ schwerwiegende Folgen haben. Es kann alle Arten von undefiniertem Verhalten und Abstürzen verursachen, da Sie in einer Situation gefangen sein können, in der Sie mehr als eine aktive Ausnahme im Flug haben. ( Weitere Informationen hierzu finden Sie unter Punkt 8 von Effective C ++ .)


4
+1 für effektives C ++. Lesen Sie die 3. Ausgabe, während wir sprechen. Ausnahmen sind für mich in Ordnung, solange Sie sehr genau wissen, wo Sie sie verwenden, wo Sie sie fangen und Sie können garantieren, dass jeder Code von Drittanbietern dasselbe tut.
Anthony

9

Joel über die Ansichten von Software zu Ausnahmen.

Interessante Ansicht derzeit in keiner der Antworten,

Der Grund dafür ist, dass ich Ausnahmen nicht besser finde als "goto's", die seit den 1960er Jahren als schädlich angesehen werden, da sie einen abrupten Sprung von einem Codepunkt zum anderen bewirken. Tatsächlich sind sie bedeutend schlechter als die von goto:

  1. Sie sind im Quellcode unsichtbar. Wenn Sie sich einen Codeblock ansehen, einschließlich Funktionen, die Ausnahmen auslösen können oder nicht, können Sie nicht erkennen, welche Ausnahmen von wo ausgelöst werden können. Dies bedeutet, dass selbst eine sorgfältige Codeüberprüfung keine potenziellen Fehler aufdeckt.

  2. Sie erstellen zu viele mögliche Austrittspunkte für eine Funktion. Um korrekten Code zu schreiben, müssen Sie wirklich über jeden möglichen Codepfad durch Ihre Funktion nachdenken. Jedes Mal, wenn Sie eine Funktion aufrufen, die eine Ausnahme auslösen kann und diese nicht sofort abfängt, können Überraschungsfehler auftreten, die durch Funktionen verursacht werden, die abrupt beendet wurden und Daten in einem inkonsistenten Zustand belassen, oder durch andere Codepfade, die Sie nicht ausgeführt haben nachdenken über.

http://www.joelonsoftware.com/items/2003/10/13.html


Halleluja ... Großartige Referenz. Ich hatte aus genau diesem Grund eine Abneigung mit Ausnahmen, aber anscheinend ist es heutzutage die bevorzugte Art, Dinge zu tun. Schön, einen schönen Artikel zu sehen, der eine andere Ansicht gibt
Toad

1
+1, da Ausnahmen ein viel zu hohes Risiko haben, um zu spät zu scheitern, dh wenn das Spiel bereits ausgeliefert wurde.
Martinpi

Ich stimme Punkt 2 voll und ganz zu. Ich werde nicht gegen Sprachen kämpfen, die durchgehend Ausnahmen verwenden, aber ich halte sie auch nicht für den „richtigen“ Weg, um mit Fehlerbedingungen umzugehen.
Kylotan

5

Ausnahmen werden in mindestens einer modernen Konsolenentwicklungsumgebung nicht unterstützt und daher dringend empfohlen.

Die meisten Konsolenentwickler, die ich kenne, ziehen es vor, sie sowieso nicht zu verwenden, da die ausführbare Datei zusätzlichen Aufwand verursacht und die Philosophie, dass sie einfach nicht benötigt werden. RTTI wird genauso gesehen.


Auf welcher Konsole wird es nicht unterstützt?
David Young

Ich versuche wegen NDAs schüchtern zu sein, obwohl es zuvor genauer auf SO enthüllt wurde.
Dan Olson

2

Von allen Projekten, an denen ich in Spielen gearbeitet habe, verwendete keines Ausnahmen. Der Funktionsaufruf-Overhead ist der Hauptgrund. Wie bereits erwähnt, ist es wie RTTI in den meisten Studios kein Diskussionsthema. Nicht, weil die meisten Programmierer einen Java / Akademiker-Hintergrund haben, denn ehrlich gesagt, die meisten nicht.
Es wirkt sich nicht wirklich auf Unit-Tests aus, da Sie nur die Bedingungen festlegen. Bei Bibliotheken ist es ausnahmslos besser, wenn Sie es jedem aufzwingen, der es verwendet.
Während es einige Situationen etwas schwieriger macht, damit umzugehen, zwingt es Sie (imo) auch zu einer kontrollierten Einrichtung, da Ausnahmen zu Missbrauch führen können. Fehlertoleranz ist nett, aber nicht, wenn sie zu schlampiger Codierung führt.
Wohlgemerkt, das kommt von jemandem, der nie die Ausnahmebehandlung verwendet hat (okay, ein- oder zweimal in Tools - es hat es nicht für mich getan, ich denke, es ist das, womit Sie aufgewachsen sind).


1
Sie haben Recht, aber leider verwenden die meisten Spieleprogrammierer keine alternative Fehlerbehandlung, sondern stürzen einfach wieder ab oder geben NULL zurück (was wahrscheinlich sowieso zu einem Absturz führen wird). Wir haben keinen anständigen Ersatz gefunden.
Tenpn

Die Rückgabe von NULL oder eines Fehlercodes ist ein durchaus vernünftiger Ersatz, solange Sie auch die Rückkehrcodes überprüfen.
JasonD

Ja, aber die meisten Spieleprogrammierer tun das nicht. Welches ist mein Punkt.
Tenpn

1
Es ist vor allem ein Problem der C ++ - Sprache - wenn Sie eine Codezeile sehen, die besagt, dass function();Sie nicht sofort wissen, ob ein Fehlercode ignoriert wird oder nicht. Dies ist vermutlich der Grund, warum Sprachen, mit denen Sie einen Rückgabewert ignorieren können, versuchen, Sie dazu zu zwingen, Ausnahmen zu bevorzugen.
Kylotan

1
Die Durchsetzung von Fehlerargumenten ist kein Problem mehr. Moderner Code, der keine Ausnahmen verwendet Error, basiert auf einer Klasse, die leichtgewichtig ist. Wenn sie ignoriert wird, wird eine Zusicherung ausgelöst. Es kann also wirklich nicht ignoriert werden, nicht mehr als eine Ausnahme kann ignoriert werden.
Samaursa

1

Das Wichtigste für mich als professioneller Spieleentwickler ist, dass Ausnahmen von Leuten geschrieben werden müssen, die verstehen, wie Ausnahmen funktionieren (was es in der Spielebranche nur wenige gibt), die auch von ihnen debuggt werden, und den zugrunde liegenden Code, der sie ausführt (was ist) Ein verzweigtes Durcheinander, das Ihren Prozessor in der richtigen Reihenfolge tötet, musste von Leuten geschrieben werden, die Ihren Compiler / Linker bereitstellen. Das Problem dabei ist, dass sie nur eine begrenzte Menge an Ressourcen haben, also konzentrieren sie sich darauf, bessere Optimierungen und Korrekturen für Code zu schreiben, den Spieleentwickler verwenden ... was normalerweise keine Ausnahmen sind. Wen werden Sie anrufen, wenn Sie einen Ausnahmefehler bei moderner Hardware mit neuen Compilern haben? Ihr Ausnahmeexperte, der der Meinung ist, dass mit dem Code alles in Ordnung ist, oder der Anbieter, der sagt: Ja, bald, wir '

Mit Ausnahmen ist nichts an sich falsch, nur sind sie nicht gut, weil die Realität der Theorie im Wege steht.


0

Für mich kann die Ausnahmebehandlung großartig sein, wenn alles RAII-konform ist und Sie Ausnahmen für wirklich außergewöhnliche Pfade reservieren (z. B. eine beschädigte Datei, die gelesen wird), und Sie niemals Modulgrenzen überschreiten müssen und Ihr gesamtes Team an Bord ist ......

Zero-Cost EH

Die meisten Compiler implementieren heutzutage eine kostengünstige Ausnahmebehandlung, die es sogar billiger machen kann als die manuelle Verzweigung unter Fehlerbedingungen in regulären Ausführungspfaden, obwohl im Gegenzug außergewöhnliche Pfade enorm teuer werden (es gibt auch einige Code-Aufblähungen, wenn auch wahrscheinlich nicht mehr als wenn Sie jeden möglichen Fehler gründlich manuell behandelt hätten). Die Tatsache, dass das Werfen enorm teuer ist, sollte jedoch keine Rolle spielen, wenn Ausnahmen so verwendet werden, wie sie in C ++ vorgesehen sind (für wirklich außergewöhnliche Umstände, die unter normalen Bedingungen nicht auftreten sollten).

Umkehrung von Nebenwirkungen

Trotzdem ist es viel einfacher gesagt als getan, alles an RAII anzupassen. Es ist für lokale Ressourcen, die in einer Funktion gespeichert sind, einfach, sie in C ++ - Objekte mit Destruktoren zu verpacken, die sich selbst bereinigen, aber es ist nicht so einfach, eine Rückgängig- / Rollback-Logik für jeden einzelnen Nebeneffekt zu schreiben, der in der gesamten Software auftreten kann. Überlegen Sie nur, wie schwierig es ist, einen Container zu schreiben, std::vectorder die einfachste Sequenz mit wahlfreiem Zugriff darstellt, um absolut ausnahmesicher zu sein. Multiplizieren Sie diese Schwierigkeit nun über alle Datenstrukturen einer gesamten großen Software.

Betrachten Sie als grundlegendes Beispiel eine Ausnahme, die beim Einfügen von Elementen in ein Szenendiagramm auftritt. Um diese Ausnahme ordnungsgemäß zu beheben, müssen Sie möglicherweise diese Änderungen im Szenendiagramm rückgängig machen und das System wieder in einen Zustand versetzen, in dem der Vorgang nie stattgefunden hat. Das bedeutet, dass die in das Szenendiagramm eingefügten untergeordneten Elemente automatisch entfernt werden, wenn eine Ausnahme auftritt, möglicherweise von einem Scope Guard.

Dies gründlich in einer Software zu tun, die viele Nebenwirkungen verursacht und mit vielen dauerhaften Zuständen umgeht, ist viel einfacher gesagt als getan. Oft denke ich, dass die pragmatische Lösung darin besteht, sich nicht darum zu kümmern, um die Dinge zu versenden. Mit der richtigen Art von Codebasis, die über ein zentrales Rückgängig-System verfügt, und möglicherweise dauerhaften Datenstrukturen und der minimalen Anzahl von Funktionen, die Nebenwirkungen verursachen, können Sie möglicherweise auf ganzer Linie Ausnahmesicherheit erreichen ... aber es ist eine Menge Dinge, die Sie brauchen, wenn Sie nur versuchen wollen, Ihren Code überall ausnahmesicher zu machen.

Dort stimmt praktisch etwas nicht, da die konzeptionelle Umkehrung von Nebenwirkungen ein schwieriges Problem darstellt, das in C ++ jedoch schwieriger wird. Manchmal ist es tatsächlich einfacher, Nebenwirkungen in C als Reaktion auf einen Fehlercode umzukehren, als als Reaktion auf eine Ausnahme in C ++. Einer der Gründe ist, dass jeder Punkt einer Funktion möglicherweise throwin C ++ vorhanden sein kann. Wenn Sie versuchen, einen generischen Container zu schreiben, der mit type arbeitet T, können Sie nicht einmal vorhersehen, wo sich diese Austrittspunkte befinden, da alles, Twas dazu gehört, werfen könnte. Sogar ein Vergleich Tuntereinander für Gleichheit könnte werfen. Ihr Code muss alle Möglichkeiten bewältigen , und die Anzahl der Möglichkeiten multipliziert sich in C ++ exponentiell, während sie in C viel weniger sind.

Fehlende Standards

Ein weiteres Problem der Ausnahmebehandlung ist das Fehlen zentraler Standards. Es gibt einige, denen hoffentlich alle zustimmen, dass Destruktoren niemals werfen sollten, da Rollbacks niemals fehlschlagen sollten, aber das deckt nur einen pathologischen Fall ab, der die Software im Allgemeinen zum Absturz bringen würde, wenn Sie gegen die Regel verstoßen.

Es sollte vernünftigere Standards geben, wie niemals von einem Vergleichsoperator werfen (alle Funktionen, die logisch unveränderlich sind, sollten niemals werfen), niemals von einem Verschiebungs-ctor werfen usw. Solche Garantien würden es so viel einfacher machen, ausnahmesicheren Code zu schreiben. Wir haben jedoch keine solchen Garantien. Wir müssen uns an die Regel halten, dass alles werfen könnte - wenn nicht jetzt, dann möglicherweise in der Zukunft.

Schlimmer noch, Menschen mit unterschiedlichem Sprachhintergrund betrachten Ausnahmen sehr unterschiedlich. In Python haben sie tatsächlich diese "Sprung vor dem Blick" -Philosophie, bei der Ausnahmen in regulären Ausführungspfaden ausgelöst werden! Wenn Sie dann solche Entwickler dazu bringen, C ++ - Code zu schreiben, könnten Sie nach Ausnahmen suchen, die für Dinge, die nicht wirklich außergewöhnlich sind, nach links und rechts geworfen werden, und alles zu handhaben kann ein Albtraum sein, wenn Sie versuchen, generischen Code zu schreiben, z

Fehlerbehandlung

Die Fehlerbehandlung hat den Nachteil, dass Sie nach jedem möglichen Fehler suchen müssen, der auftreten könnte. Es hat jedoch den Vorteil, dass Sie im Nachhinein manchmal etwas spät nach Fehlern suchen können glGetError, um beispielsweise die Austrittspunkte in einer Funktion erheblich zu reduzieren und sie explizit zu machen. Manchmal können Sie den Code etwas länger ausführen, bevor Sie einen Fehler überprüfen, weitergeben und beheben, und manchmal ist dies wirklich einfacher als die Behandlung von Ausnahmen (insbesondere bei Umkehrung von Nebenwirkungen). Aber vielleicht beschäftigen Sie sich auch nicht so sehr mit Fehlern in einem Spiel, sondern schließen das Spiel möglicherweise nur mit einer Meldung, wenn Sie auf beschädigte Dateien, Speicherfehler usw. stoßen.

Wie es sich um die Entwicklung einer Bibliothek handelt, die in mehreren Projekten verwendet wird.

Ein unangenehmer Teil von Ausnahmen in DLL-Kontexten ist, dass Sie Ausnahmen nicht sicher von einem Modul auf ein anderes werfen können, es sei denn, Sie können garantieren, dass sie von demselben Compiler erstellt werden, dieselben Einstellungen verwenden usw. Wenn Sie also wie eine Plugin-Architektur schreiben Ausnahmen, die für Benutzer aller Arten von Compilern und möglicherweise sogar aus verschiedenen Sprachen wie Lua, C, C #, Java usw. verwendet werden sollen, werden häufig zu einem großen Ärgernis, da Sie jede Ausnahme verschlucken und in Fehler übersetzen müssen Codes sowieso überall.

Was bedeutet es, Bibliotheken von Drittanbietern zu verwenden?

Wenn es sich um Dylibs handelt, können sie aus den oben genannten Gründen keine Ausnahmen sicher auslösen. Sie müssten Fehlercodes verwenden, was auch bedeutet, dass Sie bei Verwendung der Bibliothek ständig nach Fehlercodes suchen müssen. Sie könnten ihre Dylib mit einer statisch verknüpften C ++ - Wrapper-Bibliothek umschließen, die Sie selbst erstellen und die Fehlercodes aus der Dylib in ausgelöste Ausnahmen übersetzt (im Grunde genommen aus Ihrer Binärdatei in Ihre Binärdatei). Im Allgemeinen denke ich, dass die meisten Bibliotheken von Drittanbietern sich nicht einmal darum kümmern sollten und sich nur an Fehlercodes halten sollten, wenn sie Dylibs / Shared Libs verwenden.

Wie wirkt es sich auf Unit-Tests, Integrationstests usw. aus?

Ich treffe im Allgemeinen nicht so oft auf Teams, die außergewöhnliche / fehlerhafte Pfade ihres Codes testen. Früher habe ich es getan und hatte tatsächlich Code, von dem bad_allocich mich richtig erholen konnte, weil ich meinen Code extrem robust machen wollte, aber es hilft nicht wirklich, wenn ich der einzige bin, der es in einem Team tut. Am Ende hörte ich auf zu stören. Ich stelle mir für eine unternehmenskritische Software vor, dass ganze Teams prüfen könnten, ob ihr Code in allen möglichen Fällen zuverlässig von Ausnahmen und Fehlern wiederhergestellt wird, aber das ist viel Zeit für Investitionen. Die Zeit macht in unternehmenskritischer Software Sinn, aber vielleicht kein Spiel.

Hassliebe

Ausnahmen sind auch meine Art von Liebes- / Hass-Funktion von C ++ - eine der wirkungsvollsten Funktionen der Sprache, die RAII eher zu einer Anforderung als zu einer Annehmlichkeit macht, da es die Art und Weise ändert, wie Sie Code verkehrt herum schreiben müssen , C, um die Tatsache zu behandeln, dass jede gegebene Codezeile ein impliziter Austrittspunkt für eine Funktion sein könnte. Manchmal wünschte ich mir fast, die Sprache hätte keine Ausnahmen. Manchmal finde ich Ausnahmen wirklich nützlich, aber sie sind für mich ein zu dominierender Faktor, wenn ich einen Code in C oder C ++ schreibe.

Sie wären für mich exponentiell nützlicher, wenn sich das Standardkomitee wirklich stark auf die Festlegung von ABI-Standards konzentrieren würde, die es ermöglichen, Ausnahmen sicher über Module hinweg zu werfen, zumindest von C ++ bis C ++ - Code. Es wäre großartig, wenn Sie sicher über Sprachen hinwegwerfen könnten, obwohl das eine Art Wunschtraum ist. Die andere Sache, die Ausnahmen für mich exponentiell nützlicher machen würde, ist, wenn noexceptFunktionen tatsächlich einen Compilerfehler verursachten, wenn sie versuchten, Funktionen aufzurufen, die ausgelöst werden könnten, anstatt nur std::terminateauf eine Ausnahme zu stoßen. Das ist so gut wie nutzlos (könnte das genauso gut nennen, icantexceptanstatt tatsächlich einen Compilerfehler zu verursachen, wenn der Destruktor irgendetwas getan hat, das werfen könnte. Wenn das zu teuer ist, um es zur Kompilierungszeit gründlich zu bestimmen, dann machen Sie es einfach sonoexcept ). Es wäre viel einfacher sicherzustellen, dass alle Destruktoren ausnahmesicher sind, zum Beispiel, wennnoexceptnoexceptFunktionen (die alle Destruktoren implizit sein sollten) dürfen nur ebenfalls noexceptFunktionen / Operatoren aufrufen und es zu einem Compilerfehler machen, von einem von ihnen zu werfen.


-2

Meines Erachtens gibt es zwei Hauptgründe, warum es in der Spieleentwicklung traditionell nicht verwendet wird:

  • Die meisten Spielprogrammierer sind entweder Absolventen, die Java und Haskell gelernt haben, oder C-Programmierer, die keine Erfahrung mit Ausnahmen haben. Daher haben sie große Angst vor ihnen und wissen nicht, wie sie richtig eingesetzt werden sollen.
  • Das Abwickeln des Stapels, wenn eine Ausnahme ausgelöst wird, hat Auswirkungen auf die Leistung (obwohl dies eine Art empfangene Weisheit ist und einige Referenzen großartig wären).

3
Java-Programmierer wissen nicht, wie man Ausnahmen verwendet? Java basiert auf der Ausnahmebehandlung. Selbst die grundlegendsten Java-Programmierer verstehen die Blöcke Try, Catch, finally. Sie können in Java wirklich nicht programmieren, ohne Ausnahmen auszulösen oder zu behandeln.
David Young

4
Es ist mehr als nur das Abwickeln des Stapels. Ausnahmen erhöhen den Overhead für jeden Funktionsaufruf. codeproject.com/KB/cpp/exceptionhandler.aspx
Jonathan Fischoff

@ David Young Ich spreche mehr über die starken / schwachen Garantien und die komplexere Seite der Verwendung von Ausnahmen, mit denen Hochschulabsolventen möglicherweise nicht vertraut sind. Um das noch zu verschärfen, sind viele Universitäten viel mehr daran interessiert, wie Sie Ihre OOP so strukturieren Legen Sie weniger Wert darauf, den richtigen Umgang mit Java-Ausnahmen zu erlernen. Aber du hast recht, ich war nicht klar.
Tenpn

Vielleicht ist "Angst" auch das falsche Wort. :) :)
Tenpn

3
Ich würde sagen, sie sind oft das Gegenteil von "Angst" - wenn Sie keine strenge Regel "keine Ausnahmen, keine Ausnahmen" haben, werden die jüngsten Java-Absolventen häufiger Ausnahmen werfen als zurückkehren, genau wie Anfänger-C-Programmierer Verwenden Sie goto und break, anstatt gute Schleifeninvarianten zu schreiben.
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.