Interessante Antworten: Obwohl ich (bisher) allen zustimme, gibt es mögliche Konnotationen zu dieser Frage, die bisher völlig außer Acht gelassen wurden.
Wenn das obige einfache Beispiel um die Ressourcenzuweisung und die Fehlerprüfung mit einer möglichen daraus resultierenden Ressourcenfreigabe erweitert wird, kann sich das Bild ändern.
Betrachten Sie den naiven Ansatz, den Anfänger verfolgen könnten:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
Das Obige würde eine extreme Version des Stils der vorzeitigen Rückkehr darstellen. Beachten Sie, dass sich der Code im Laufe der Zeit sehr wiederholt und nicht mehr gewartet werden kann, wenn seine Komplexität zunimmt. Heutzutage könnten Leute die Ausnahmebehandlung verwenden , um diese zu fangen.
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
Philip schlug vor, nachdem er sich das folgende Beispiel angesehen zu haben, einen unterbrechungsfreien Schalter / Koffer im oberen Fangblock zu verwenden. Man könnte wechseln (typeof (e)) und dann durch die free_resourcex()
Anrufe fallen, aber dies ist nicht trivial und erfordert Designüberlegungen . Und denken Sie daran, dass ein Schalter / Gehäuse ohne Unterbrechungen genau wie das Goto mit verketteten Etiketten unten ist ...
Wie Mark B hervorhob, wird es in C ++ als guter Stil angesehen, dem Prinzip der Ressourcenerfassung als Initialisierung zu folgen , kurz RAII . Der Kern des Konzepts besteht darin, die Objektinstanziierung zu verwenden, um Ressourcen zu erwerben. Die Ressourcen werden dann automatisch freigegeben, sobald die Objekte den Gültigkeitsbereich verlassen und ihre Destruktoren aufgerufen werden. Bei voneinander abhängigen Ressourcen muss besonders darauf geachtet werden, die richtige Reihenfolge der Freigabe sicherzustellen und die Objekttypen so zu gestalten, dass die erforderlichen Daten für alle Destruktoren verfügbar sind.
Oder in Tagen vor der Ausnahme könnte Folgendes passieren:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
Dieses stark vereinfachte Beispiel weist jedoch mehrere Nachteile auf: Es kann nur verwendet werden, wenn die zugewiesenen Ressourcen nicht voneinander abhängen (z. B. kann es nicht zum Zuweisen von Speicher, zum Öffnen eines Dateihandles und zum Einlesen von Daten aus dem Handle in den Speicher verwendet werden ), und es werden keine einzelnen, unterscheidbaren Fehlercodes als Rückgabewerte bereitgestellt.
Um den Code schnell (!), Kompakt, leicht lesbar und erweiterbar zu halten, hat Linus Torvalds einen anderen Stil für Kernel-Code erzwungen, der sich mit Ressourcen befasst, selbst wenn das berüchtigte goto auf eine absolut sinnvolle Weise verwendet wird :
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
Der Kern der Diskussion über die Kernel-Mailinglisten ist, dass die meisten Sprachfunktionen, die der goto-Anweisung "vorgezogen" werden, implizite gotos sind, wie z. B. riesige, baumartige if / else-Ausnahmebehandlungsroutinen, Schleifen- / Unterbrechungs- / Fortsetzungsanweisungen usw. Und gotos im obigen Beispiel gelten als in Ordnung, da sie nur eine kleine Strecke springen, klare Beschriftungen haben und den Code anderer Unordnung freigeben, um die Fehlerbedingungen zu verfolgen. Diese Frage wurde auch hier zum Stackoverflow diskutiert .
Was jedoch im letzten Beispiel fehlt, ist eine gute Möglichkeit, einen Fehlercode zurückzugeben. Ich dachte daran, result_code++
nach jedem free_resource_x()
Aufruf ein hinzuzufügen und diesen Code zurückzugeben, aber dies gleicht einige der Geschwindigkeitsgewinne des obigen Codierungsstils aus. Und es ist schwierig, im Erfolgsfall 0 zurückzugeben. Vielleicht bin ich nur einfallslos ;-)
Ja, ich glaube, es gibt einen großen Unterschied in der Frage, ob vorzeitige Rückgaben codiert werden sollen oder nicht. Ich denke aber auch, dass es nur in komplizierterem Code offensichtlich ist, dass es schwieriger oder unmöglich ist, den Compiler neu zu strukturieren und zu optimieren. Dies ist normalerweise der Fall, sobald die Ressourcenzuweisung ins Spiel kommt.