it-swarm.com.de

Warum sind Ausnahmespezifikationen schlecht?

Vor mehr als 10 Jahren in der Schule haben sie Ihnen beigebracht, Ausnahmespezifizierer zu verwenden. Da ich als einer von ihnen torvaldische C-Programmierer bin, die C++ hartnäckig meiden, wenn sie nicht dazu gezwungen werden, lande ich nur sporadisch in C++, und wenn ich dies tue, verwende ich immer noch Ausnahmespezifizierer, da mir dies beigebracht wurde.

Die Mehrheit der C++ - Programmierer scheint jedoch Ausnahmespezifizierer zu missbilligen. Ich habe die Debatte und die Argumente von verschiedenen C++ - Gurus gelesen, wie diese . Soweit ich es verstehe, läuft es auf drei Dinge hinaus:

  1. Ausnahmespezifizierer verwenden ein Typsystem, das nicht mit dem Rest der Sprache übereinstimmt ("Schattentypsystem").
  2. Wenn Ihre Funktion mit einem Ausnahmespezifizierer etwas anderes als das von Ihnen angegebene auslöst, wird das Programm auf schlechte, unerwartete Weise beendet.
  3. Ausnahmespezifizierer werden im kommenden C++ - Standard entfernt.

Vermisse ich hier etwas oder sind das alle Gründe?

Meine eigene Meinung:

Zu 1): Na und. C++ ist wahrscheinlich die inkonsistenteste Programmiersprache, die jemals in Bezug auf die Syntax erstellt wurde. Wir haben die Makros, die Goto/Labels, die Horde (Hort?) Des undefinierten/nicht spezifizierten/implementierungsdefinierten Verhaltens, die schlecht definierten Integer-Typen, alle impliziten Regeln für die Typ-Promotion, Sonderfall-Schlüsselwörter wie friend, auto , registrieren, explizit ... Und so weiter. Jemand könnte wahrscheinlich mehrere dicke Bücher über all die Verrücktheiten in C/C++ schreiben. Warum reagieren die Menschen also auf diese besondere Inkonsistenz, die im Vergleich zu vielen anderen weitaus gefährlicheren Merkmalen der Sprache ein kleiner Fehler ist?

Zu 2): Ist das nicht meine eigene Verantwortung? Es gibt so viele andere Möglichkeiten, wie ich einen schwerwiegenden Fehler in C++ schreiben kann. Warum ist dieser spezielle Fall noch schlimmer? Anstatt throw(int) zu schreiben und dann Crash_t zu werfen, kann ich auch behaupten, dass meine Funktion einen Zeiger auf int zurückgibt, dann einen wilden, expliziten Typecast erstellt und einen Zeiger auf einen Crash_t zurückgibt. Der Geist von C/C++ war es immer, den größten Teil der Verantwortung dem Programmierer zu überlassen.

Was ist dann mit den Vorteilen? Am offensichtlichsten ist, dass der Compiler einen Fehler ausgibt, wenn Ihre Funktion versucht, einen anderen als den von Ihnen angegebenen Typ explizit auszulösen. Ich glaube, dass der Standard diesbezüglich klar ist (?). Fehler treten nur auf, wenn Ihre Funktion andere Funktionen aufruft, die wiederum den falschen Typ auslösen.

Ich komme aus einer Welt deterministischer, eingebetteter C-Programme und würde es mit Sicherheit vorziehen, genau zu wissen, was eine Funktion auf mich werfen wird. Wenn die Sprache dies unterstützt, warum nicht? Die Alternativen scheinen zu sein:

void func() throw(Egg_t);

und

void func(); // This function throws an Egg_t

Ich denke, es besteht eine große Chance, dass der Anrufer ignoriert/vergisst, den Try-Catch im zweiten Fall zu implementieren, weniger im ersten Fall.

Soweit ich weiß, stürzt das Programm ab, wenn eine dieser beiden Formen plötzlich eine andere Ausnahme auslöst. Im ersten Fall, weil es nicht erlaubt ist, eine weitere Ausnahme auszulösen, im zweiten Fall, weil niemand damit gerechnet hat, dass ein SpanishInquisition_t ausgelöst wird, und dieser Ausdruck daher nicht dort abgefangen wird, wo er hätte sein sollen.

Im letzteren Fall scheint es nicht besser zu sein, einen letzten Ausweg (...) auf der höchsten Ebene des Programms zu haben als einen Programmabsturz: "Hey, irgendwo in Ihrem Programm hat etwas eine seltsame, unbehandelte Ausnahme ausgelöst . ". Sie können das Programm nicht wiederherstellen, wenn Sie so weit von der Stelle entfernt sind, an der die Ausnahme ausgelöst wurde. Sie können das Programm nur beenden.

Und aus Sicht des Benutzers ist es ihm egal, ob er vom Betriebssystem eine Meldungsbox mit der Aufschrift "Programm beendet. Blablabla unter der Adresse 0x12345" oder eine Meldungsbox mit der Meldung "Nicht behandelte Ausnahme: myclass" erhält. func.something ". Der Fehler ist immer noch da.


Mit dem kommenden C++ - Standard habe ich keine andere Wahl, als Ausnahmespezifizierer aufzugeben. Aber ich würde lieber ein solides Argument hören, warum sie schlecht sind, als "Seine Heiligkeit hat es gesagt und so ist es so". Vielleicht gibt es mehr Argumente gegen sie als die, die ich aufgelistet habe, oder vielleicht gibt es mehr gegen sie, als mir klar ist?

51
user29079

Ausnahmespezifikationen sind schlecht, weil sie schwach durchgesetzt werden und daher nicht viel bewirken, und sie sind auch schlecht, weil sie die Laufzeit zwingen, nach unerwarteten Ausnahmen zu suchen, damit sie terminieren können (), anstatt UB aufzurufen Dies kann eine erhebliche Menge an Leistung verschwenden.

Zusammenfassend lässt sich sagen, dass Ausnahmespezifikationen in der Sprache nicht stark genug durchgesetzt werden, um den Code tatsächlich sicherer zu machen, und dass die Implementierung wie angegeben ein großer Leistungsverlust war.

50
DeadMG

Ein Grund, warum niemand sie verwendet, ist, dass Ihre Hauptannahme falsch ist:

"Der offensichtlichste [Vorteil] ist, dass der Compiler einen Fehler ausgibt, wenn Ihre Funktion versucht, einen anderen als den von Ihnen angegebenen Typ explizit auszulösen."

struct foo {};
struct bar {};

struct test
{
    void baz() throw(foo)
    {
        throw bar();
    }
};

int main()
{
    test x;
    try { x.baz(); } catch(bar &b) {}
}

Dieses Programm kompiliert mit keine Fehler oder Warnungen.

Darüber hinaus wird das Programm trotzdem beendet, obwohl die Ausnahme abgefangen worden wäre .


Bearbeiten: Um einen Punkt in Ihrer Frage zu beantworten, fangen Sie (...) (oder besser fangen Sie (std :: exeption &) oder eine andere Basisklasse und dann catch (...)) ist immer noch nützlich, auch wenn Sie nicht genau wissen, was schief gelaufen ist.

Beachten Sie, dass Ihr Benutzer eine Menüschaltfläche für "Speichern" gedrückt hat. Der Handler für die Menüschaltfläche lädt die Anwendung zum Speichern ein. Dies kann aus unzähligen Gründen fehlschlagen: Die Datei befand sich in einer verschwundenen Netzwerkressource, es gab eine schreibgeschützte Datei und sie konnte nicht gespeichert werden usw. Dem Handler ist es jedoch egal, warum etwas fehlgeschlagen ist. es ist nur wichtig, dass es entweder erfolgreich war oder fehlgeschlagen ist. Wenn es gelungen ist, ist alles gut. Wenn es fehlgeschlagen ist, kann es den Benutzer informieren. Die genaue Art der Ausnahme spielt keine Rolle. Wenn Sie richtigen, ausnahmesicheren Code schreiben, bedeutet dies außerdem, dass sich ein solcher Fehler über Ihr System ausbreiten kann, ohne ihn zu beheben - selbst für Code, der sich nicht um den Fehler kümmert. Dies erleichtert die Erweiterung des Systems. Beispielsweise speichern Sie jetzt über eine Datenbankverbindung. Werden Sie den Wurf (SQLException) im Funktionsstapel ganz nach oben verbreiten oder ihn einfach als "Fehler" klassifizieren und ihn bereits zusammen mit allen anderen Dingen, die schief gehen könnten, richtig behandeln?

19
Kaz Dragon

Dieses Interview mit Anders Hejlsberg ist ziemlich berühmt. Darin erklärt er, warum das C # -Design-Team geprüfte Ausnahmen überhaupt verworfen hat. Kurz gesagt, es gibt zwei Hauptgründe: Versionsfähigkeit und Skalierbarkeit.

Ich weiß, dass sich das OP auf C++ konzentriert, während Hejlsberg über C # spricht, aber die Punkte, die Hejlsberg macht, sind auch für C++ perfekt anwendbar.

17
CesarGon