Wie Sie anscheinend bereits vermutet haben, bietet C ++ ohne diesen Mechanismus dieselben Funktionen. Als solches ist der try
/ finally
Mechanismus streng genommen nicht wirklich notwendig.
Das heißt, wenn Sie darauf verzichten, werden einige Anforderungen an die Gestaltung der restlichen Sprache gestellt. In C ++ ist dieselbe Menge von Aktionen in einem Klassendestruktor enthalten. Dies funktioniert hauptsächlich (ausschließlich?), Weil der Destruktoraufruf in C ++ deterministisch ist. Dies führt wiederum zu einigen recht komplexen Regeln für die Lebensdauer von Objekten, von denen einige entschieden nicht intuitiv sind.
Die meisten anderen Sprachen bieten stattdessen eine Art Garbage Collection an. Zwar gibt es umstrittene Aspekte der Speicherbereinigung (z. B. die Effizienz im Vergleich zu anderen Methoden der Speicherverwaltung), doch im Allgemeinen ist es nicht so, dass der genaue Zeitpunkt, zu dem ein Objekt vom Speicherbereiniger "bereinigt" wird, nicht direkt festgelegt wird auf den Umfang des Objekts. Dies verhindert seine Verwendung, wenn die Bereinigung deterministisch sein muss, entweder wenn sie nur für den korrekten Betrieb erforderlich ist, oder wenn es sich um Ressourcen handelt, die so kostbar sind, dass ihre Bereinigung nicht willkürlich verzögert wird. try
/ finally
bietet eine Möglichkeit für solche Sprachen, mit Situationen umzugehen, die eine deterministische Bereinigung erfordern.
Ich denke, diejenigen, die behaupten, die C ++ - Syntax für diese Funktion sei "weniger benutzerfreundlich" als die von Java, verpassen eher den Punkt. Schlimmer noch, es fehlt ein viel entscheidenderer Punkt in Bezug auf die Aufteilung der Verantwortung, der weit über die Syntax hinausgeht und wesentlich mehr mit der Gestaltung des Codes zu tun hat.
In C ++ erfolgt diese deterministische Bereinigung im Destruktor des Objekts. Das heißt, das Objekt kann (und sollte normalerweise) so gestaltet sein, dass es nach sich selbst aufräumt. Dies geht zum Kern des objektorientierten Entwurfs - eine Klasse sollte so entworfen werden, dass sie eine Abstraktion liefert und ihre eigenen Invarianten erzwingt. In C ++ macht man genau das - und eine der Invarianten, für die es sorgt, ist, dass die von diesem Objekt gesteuerten Ressourcen (alle, nicht nur der Speicher) korrekt zerstört werden, wenn das Objekt zerstört wird.
Java (und ähnliche) sind etwas anders. Während sie eine Unterstützung finalize
bieten, die theoretisch ähnliche Fähigkeiten bieten könnte, ist die Unterstützung so schwach, dass sie im Grunde unbrauchbar ist (und im Grunde genommen nie verwendet wird).
Infolgedessen muss der Client der Klasse Schritte ausführen, damit die Klasse selbst die erforderliche Bereinigung durchführen kann . Wenn wir einen ausreichend kurzsichtigen Vergleich anstellen, kann auf den ersten Blick der Eindruck entstehen, dass dieser Unterschied relativ gering ist und Java in dieser Hinsicht mit C ++ durchaus konkurrenzfähig ist. Am Ende haben wir so etwas. In C ++ sieht die Klasse ungefähr so aus:
class Foo {
// ...
public:
void do_whatever() { if (xyz) throw something; }
~Foo() { /* handle cleanup */ }
};
... und der Client-Code sieht ungefähr so aus:
void f() {
Foo f;
f.do_whatever();
// possibly more code that might throw here
}
In Java tauschen wir etwas mehr Code aus, wobei das Objekt für etwas weniger Code in der Klasse verwendet wird. Dies sieht zunächst nach einem ziemlich ausgeglichenen Kompromiss aus. In Wirklichkeit ist es weit davon entfernt aber, weil in den meisten typischen Code nur wir die Klasse in definieren einen Ort, aber wir verwenden es viele Orte. Der C ++ - Ansatz bedeutet, dass wir diesen Code nur schreiben, um die Bereinigung an einem Ort durchzuführen. Der Java-Ansatz bedeutet, dass wir diesen Code schreiben müssen, um die Bereinigung an vielen Stellen zu erledigen - an jeder Stelle, an der wir ein Objekt dieser Klasse verwenden.
Kurz gesagt, der Java-Ansatz garantiert im Grunde genommen, dass viele Abstraktionen, die wir bereitstellen möchten, "undicht" sind - jede Klasse, die eine deterministische Bereinigung erfordert, verpflichtet den Client der Klasse, über die Details zu wissen, was bereinigt werden soll und wie die Bereinigung durchgeführt werden soll und nicht, dass diese Details in der Klasse selbst versteckt sind.
Obwohl ich es oben als "Java-Ansatz" bezeichnet habe, sind try
/ finally
und ähnliche Mechanismen unter anderen Namen nicht vollständig auf Java beschränkt. Für ein prominentes Beispiel bieten die meisten (alle?) .NET-Sprachen (z. B. C #) dasselbe.
Jüngste Iterationen von Java und C # bieten in dieser Hinsicht auch eine Halbwertszeit zwischen "klassischem" Java und C ++. In C # kann ein Objekt, das seine Bereinigung automatisieren möchte, die IDisposable
Schnittstelle implementieren , die eine Dispose
Methode bereitstellt, die einem C ++ - Destruktor (zumindest vage) ähnlich ist. Während dies in Java über a / like verwendet werden kann , automatisiert C # die Aufgabe ein wenig mehr mit einer Anweisung, mit der Sie Ressourcen definieren können, die beim Eingeben eines Bereichs erstellt und beim Verlassen des Bereichs zerstört werden. Obwohl C ++ noch weit hinter dem Automatisierungsgrad und der Sicherheit zurückbleibt, ist dies eine erhebliche Verbesserung gegenüber Java. Insbesondere kann die Klasse Designer zentralisieren die Details , wietry
finally
using
die Klasse in ihrer Umsetzung zu veräußern IDisposable
. Alles, was dem Client-Programmierer bleibt, ist die geringere Belastung durch das Schreiben einer using
Anweisung, um sicherzustellen, dass die IDisposable
Schnittstelle zum richtigen Zeitpunkt verwendet wird. In Java 7 und neuer wurden die Namen geändert, um die Schuldigen zu schützen, aber die Grundidee ist im Grunde identisch.