Unterstützt C ++ ' finally " -Blöcke?
Was ist der RAII-Sprache ?
Was ist der Unterschied zwischen der RAII-Sprache von C ++ und der using-Anweisung von C # ? ?
Unterstützt C ++ ' finally " -Blöcke?
Was ist der RAII-Sprache ?
Was ist der Unterschied zwischen der RAII-Sprache von C ++ und der using-Anweisung von C # ? ?
Antworten:
Nein, C ++ unterstützt keine 'finally'-Blöcke. Der Grund dafür ist, dass C ++ stattdessen RAII unterstützt: "Resource Acquisition Is Initialization" - ein schlechter Name † für ein wirklich nützliches Konzept.
Die Idee ist, dass der Destruktor eines Objekts für die Freigabe von Ressourcen verantwortlich ist. Wenn das Objekt eine automatische Speicherdauer hat, wird der Destruktor des Objekts aufgerufen, wenn der Block, in dem es erstellt wurde, beendet wird - auch wenn dieser Block bei Vorhandensein einer Ausnahme beendet wird. Hier ist die Erklärung von Bjarne Stroustrup des Themas.
Eine häufige Verwendung für RAII ist das Sperren eines Mutex:
RAII vereinfacht auch die Verwendung von Objekten als Mitglieder anderer Klassen. Wenn die besitzende Klasse 'zerstört wird, wird die von der RAII-Klasse verwaltete Ressource freigegeben, da der Destruktor für die von RAII verwaltete Klasse als Ergebnis aufgerufen wird. Dies bedeutet, dass Sie bei Verwendung von RAII für alle Mitglieder einer Klasse, die Ressourcen verwalten, einen sehr einfachen, möglicherweise sogar standardmäßigen Destruktor für die Eigentümerklasse verwenden können, da die Lebensdauer der Mitgliederressourcen nicht manuell verwaltet werden muss . (Vielen Dank an Mike B für diesen Hinweis.)
Für diejenigen, die mit C # oder VB.NET vertraut sind, können Sie erkennen, dass RAII der deterministischen Zerstörung von .NET mit IDisposable- und 'using'-Anweisungen ähnlich ist . In der Tat sind die beiden Methoden sehr ähnlich. Der Hauptunterschied besteht darin, dass RAII jede Art von Ressource - einschließlich Speicher - deterministisch freigibt. Bei der Implementierung von IDisposable in .NET (auch in der .NET-Sprache C ++ / CLI) werden Ressourcen mit Ausnahme des Speichers deterministisch freigegeben. In .NET wird der Speicher nicht deterministisch freigegeben. Der Speicher wird nur während der Speicherbereinigungszyklen freigegeben.
† Einige Leute glauben, dass "Zerstörung ist Ressourcenabgabe" ein genauerer Name für die RAII-Sprache ist.
quelle
In C ++ ist das schließlich wegen RAII NICHT erforderlich.
RAII überträgt die Verantwortung für die Ausnahmesicherheit vom Benutzer des Objekts auf den Designer (und Implementierer) des Objekts. Ich würde argumentieren, dass dies der richtige Ort ist, da Sie dann die Ausnahmesicherheit nur einmal korrekt ausführen müssen (im Design / in der Implementierung). Wenn Sie schließlich verwenden, müssen Sie die Ausnahmesicherheit jedes Mal korrekt einstellen, wenn Sie ein Objekt verwenden.
Auch IMO sieht der Code ordentlicher aus (siehe unten).
Beispiel:
Ein Datenbankobjekt. Um sicherzustellen, dass die DB-Verbindung verwendet wird, muss sie geöffnet und geschlossen werden. Mit RAII kann dies im Konstruktor / Destruktor erfolgen.
C ++ Wie RAII
Die Verwendung von RAII macht die korrekte Verwendung eines DB-Objekts sehr einfach. Das DB-Objekt wird sich durch die Verwendung eines Destruktors korrekt schließen, unabhängig davon, wie wir versuchen, es zu missbrauchen.
Java wie endlich
Bei der endgültigen Verwendung wird die korrekte Verwendung des Objekts an den Benutzer des Objekts delegiert. dh Es liegt in der Verantwortung des Objektbenutzers, die DB-Verbindung korrekt explizit zu schließen. Jetzt könnten Sie argumentieren, dass dies im Finalisierer möglich ist, aber Ressourcen möglicherweise nur eine begrenzte Verfügbarkeit oder andere Einschränkungen aufweisen. Daher möchten Sie im Allgemeinen die Freigabe des Objekts steuern und sich nicht auf das nicht deterministische Verhalten des Garbage Collectors verlassen.
Auch dies ist ein einfaches Beispiel.
Wenn Sie mehrere Ressourcen haben, die freigegeben werden müssen, kann der Code kompliziert werden.
Eine detailliertere Analyse finden Sie hier: http://accu.org/index.php/journals/236
quelle
// Make sure not to throw exception if one is already propagating.
Aus diesem Grund ist es für C ++ - Destruktoren wichtig, keine Ausnahmen auszulösen.RAII ist normalerweise besser, aber Sie können leicht die endgültige Semantik in C ++ haben. Mit einer winzigen Menge Code.
Außerdem geben die C ++ Core Guidelines endlich.
Hier ist ein Link zur Implementierung von GSL Microsoft und ein Link zur Implementierung von Martin Moene
Bjarne Stroustrup sagte mehrmals, dass alles, was in der GSL enthalten ist, irgendwann in den Standard aufgenommen werden soll. Es sollte also eine zukunftssichere Möglichkeit sein, sie endlich zu verwenden .
Sie können sich leicht implementieren, wenn Sie möchten, lesen Sie weiter.
In C ++ 11 erlaubt RAII und Lambdas endlich einen General zu machen:
Anwendungsbeispiel:
Die Ausgabe lautet:
Persönlich habe ich dies einige Male verwendet, um sicherzustellen, dass der POSIX-Dateideskriptor in einem C ++ - Programm geschlossen wird.
Eine echte Klasse zu haben, die Ressourcen verwaltet und so jegliche Art von Lecks vermeidet, ist normalerweise besser, aber dies ist schließlich in den Fällen nützlich, in denen das Erstellen einer Klasse wie ein Overkill klingt.
Außerdem habe ich , wie es besser als andere Sprachen endlich da , wenn verwendet , natürlich Sie den Schließcode in der Nähe der Öffnungscode (in meinem Beispiel die schreiben neue und löschen ) und Zerstörung folgt Bau in LIFO - Reihenfolge wie üblich in C ++. Der einzige Nachteil ist, dass Sie eine automatische Variable erhalten, die Sie nicht wirklich verwenden, und die Lambda-Syntax macht sie etwas verrauscht (in meinem Beispiel in der vierten Zeile sind nur das Wort finally und der {} -Block rechts von Bedeutung, die Ruhe ist im Wesentlichen Lärm).
Ein anderes Beispiel:
Das Deaktivierungsmitglied ist nützlich, wenn das endgültige nur im Fehlerfall aufgerufen werden muss. Wenn Sie beispielsweise ein Objekt in drei verschiedenen Containern kopieren müssen, können Sie die Option zum endgültigen Rückgängigmachen jeder Kopie einrichten und deaktivieren, nachdem alle Kopien erfolgreich waren. Wenn die Zerstörung nicht geworfen werden kann, stellen Sie die starke Garantie sicher.
Beispiel deaktivieren :
Wenn Sie C ++ 11 nicht verwenden können, können Sie es schließlich noch haben , aber der Code wird etwas langwieriger. Definieren Sie einfach eine Struktur mit nur einem Konstruktor und einem Destruktor, der Konstruktor nimmt Verweise auf alles, was benötigt wird, und der Destruktor führt die Aktionen aus, die Sie benötigen. Dies ist im Grunde das, was das Lambda manuell macht.
quelle
FinalAction
im Grunde dasselbe ist wie die populäreScopeGuard
Redewendung, nur mit einem anderen Namen.RAII erleichtert nicht nur die Bereinigung mit stapelbasierten Objekten, sondern ist auch nützlich, da dieselbe automatische Bereinigung erfolgt, wenn das Objekt Mitglied einer anderen Klasse ist. Wenn die besitzende Klasse zerstört wird, wird die von der RAII-Klasse verwaltete Ressource bereinigt, da der dtor für diese Klasse als Ergebnis aufgerufen wird.
Dies bedeutet, dass Sie, wenn Sie das RAII-Nirvana erreichen und alle Mitglieder einer Klasse RAII verwenden (wie intelligente Zeiger), mit einem sehr einfachen (möglicherweise sogar standardmäßigen) Dtor für die Eigentümerklasse davonkommen können, da diese nicht manuell verwaltet werden muss Lebensdauer der Mitgliederressourcen.
quelle
Tatsächlich brauchen Sprachen, die auf Garbage Collectors basieren, "endlich" mehr. Ein Garbage Collector zerstört Ihre Objekte nicht rechtzeitig, sodass Sie sich nicht darauf verlassen können, dass nicht speicherbezogene Probleme korrekt behoben werden.
In Bezug auf dynamisch zugewiesene Daten würden viele argumentieren, dass Sie Smart-Pointer verwenden sollten.
Jedoch...
Leider ist dies sein eigener Untergang. Alte C-Programmiergewohnheiten sterben schwer. Wenn Sie eine Bibliothek verwenden, die in C oder einem sehr C-Stil geschrieben ist, wurde RAII nicht verwendet. Wenn Sie nicht das gesamte API-Front-End neu schreiben, müssen Sie genau damit arbeiten. Dann beißt der Mangel an "endlich" wirklich.
quelle
CleanupFailedException
. Gibt es einen plausiblen Weg, um mit RAII ein solches Ergebnis zu erzielen?SomeObject.DoSomething()
Methode aufruft und wissen möchte, ob sie (1) erfolgreich war, (2) ohne Nebenwirkungen fehlgeschlagen ist, (3) mit Nebenwirkungen fehlgeschlagen ist, auf die der Aufrufer vorbereitet ist , oder (4) fehlgeschlagen mit Nebenwirkungen, die der Anrufer nicht bewältigen kann. Nur der Anrufer wird wissen, mit welchen Situationen er fertig werden kann und welche nicht. Was der Anrufer braucht, ist eine Möglichkeit zu wissen, wie die Situation ist. Es ist schade, dass es keinen Standardmechanismus für die Bereitstellung der wichtigsten Informationen zu einer Ausnahme gibt.Eine weitere "endgültige" Blockemulation mit C ++ 11 Lambda-Funktionen
Hoffen wir, dass der Compiler den obigen Code optimiert.
Jetzt können wir Code wie folgt schreiben:
Wenn Sie möchten, können Sie diese Redewendung in "try - finally" -Makros einbinden:
Jetzt ist der Block "finally" in C ++ 11 verfügbar:
Persönlich mag ich die "Makro" -Version von "finally" nicht und würde es vorziehen, die reine "with_finally" -Funktion zu verwenden, obwohl eine Syntax in diesem Fall sperriger ist.
Sie können den obigen Code hier testen: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Wenn Sie einen endgültigen Block in Ihrem Code benötigen , passen Scoped Guards oder ON_FINALLY / ON_EXCEPTION- Makros wahrscheinlich besser zu Ihren Anforderungen.
Hier ist ein kurzes Anwendungsbeispiel ON_FINALLY / ON_EXCEPTION:
quelle
Es tut mir leid, dass ich einen so alten Thread ausgegraben habe, aber die folgenden Überlegungen enthalten einen schwerwiegenden Fehler:
Meistens müssen Sie sich mit dynamisch zugewiesenen Objekten, dynamischer Anzahl von Objekten usw. befassen. Innerhalb des Try-Blocks kann ein Code viele Objekte erstellen (wie viele zur Laufzeit bestimmt werden) und Zeiger darauf in einer Liste speichern. Dies ist kein exotisches Szenario, aber sehr häufig. In diesem Fall möchten Sie Dinge wie schreiben
Natürlich wird die Liste selbst zerstört, wenn der Gültigkeitsbereich verlassen wird, aber das würde die von Ihnen erstellten temporären Objekte nicht bereinigen.
Stattdessen müssen Sie den hässlichen Weg gehen:
Außerdem: Warum bieten selbst verwaltete Sprachen einen endgültigen Block, obwohl Ressourcen vom Garbage Collector ohnehin automatisch freigegeben werden?
Hinweis: Mit "endlich" können Sie mehr tun als nur die Freigabe des Speichers.
quelle
new
gibt nicht NULL zurück, sondernstd::shared_ptr
undstd::unique_ptr
direkt in der stdlib.FWIW, Microsoft Visual C ++ unterstützt schließlich try und wurde in der Vergangenheit in MFC-Apps verwendet, um schwerwiegende Ausnahmen abzufangen, die andernfalls zu einem Absturz führen würden. Beispielsweise;
Ich habe dies in der Vergangenheit verwendet, um beispielsweise Sicherungen geöffneter Dateien vor dem Beenden zu speichern. Bestimmte JIT-Debugging-Einstellungen brechen diesen Mechanismus jedoch.
quelle
Wie in den anderen Antworten ausgeführt, kann C ++
finally
ähnliche Funktionen unterstützen. Die Implementierung dieser Funktionalität, die wahrscheinlich der Standardsprache am nächsten kommt, ist diejenige, die den C ++ - Kernrichtlinien beiliegt , einer Reihe von Best Practices für die Verwendung von C ++, die von Bjarne Stoustrup und Herb Sutter bearbeitet wurden. Eine Implementierung vonfinally
ist Teil der Guidelines Support Library (GSL). In allen Richtlinien wird die Verwendung vonfinally
empfohlen, wenn mit Schnittstellen alten Stils gearbeitet wird, und es gibt auch eine eigene Richtlinie mit dem Titel Verwenden Sie ein final_action-Objekt, um die Bereinigung auszudrücken, wenn kein geeignetes Ressourcenhandle verfügbar ist .C ++ unterstützt also nicht nur
finally
, es wird auch empfohlen, es in vielen gängigen Anwendungsfällen zu verwenden.Eine beispielhafte Verwendung der GSL-Implementierung würde folgendermaßen aussehen:
Die Implementierung und Verwendung von GSL ist der in der Antwort von Paolo.Bolzoni sehr ähnlich . Ein Unterschied besteht darin, dass dem von erstellten Objekt
gsl::finally()
derdisable()
Aufruf fehlt . Wenn Sie diese Funktionalität benötigen (z. B. um die Ressource zurückzugeben, sobald sie zusammengestellt ist und keine Ausnahmen auftreten müssen), bevorzugen Sie möglicherweise die Implementierung von Paolo. Andernfalls kommt die Verwendung von GSL der Verwendung standardisierter Funktionen so nahe wie möglich.quelle
Nicht wirklich, aber Sie können sie in gewissem Umfang emulieren, zum Beispiel:
Beachten Sie, dass der finally-Block möglicherweise selbst eine Ausnahme auslöst, bevor die ursprüngliche Ausnahme erneut ausgelöst wird, wodurch die ursprüngliche Ausnahme verworfen wird. Dies ist genau das gleiche Verhalten wie in einem Java-Endblock. Sie können
return
die Try & Catch-Blöcke auch nicht verwenden .quelle
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
Block heraus auszulösen .Ich habe mir ein
finally
Makro ausgedacht, das fast wie ¹ dasfinally
Schlüsselwort in Java verwendet werden kann. es nutztstd::exception_ptr
und Freunde, Lambda-Funktionen undstd::promise
, so erfordert esC++11
oder darüber; Es verwendet auch die zusammengesetzte Anweisungsausdruck- GCC-Erweiterung, die auch von clang unterstützt wird.WARNUNG : In einer früheren Version dieser Antwort wurde eine andere Implementierung des Konzepts mit vielen weiteren Einschränkungen verwendet.
Definieren wir zunächst eine Hilfsklasse.
Dann gibt es das eigentliche Makro.
Es kann wie folgt verwendet werden:
Die Verwendung von
std::promise
macht die Implementierung sehr einfach, führt aber wahrscheinlich auch zu unnötigem Overhead, der vermieden werden könnte, indem nur die erforderlichen Funktionen von neu implementiert werdenstd::promise
.¹ CAVEAT: Es gibt einige Dinge, die nicht ganz so funktionieren wie die Java-Version von
finally
. Aus dem Kopf:break
Anweisung aus den Blöcken vontry
und aus einer äußeren Schleife auszubrechencatch()
, da sie innerhalb einer Lambda-Funktion leben.catch()
Nach dem muss mindestens ein Block stehentry
: Es handelt sich um eine C ++ - Anforderung.try
und keine Rückgabe vorhanden ist, schlägt diecatch()'s
Kompilierung fehl, da dasfinally
Makro zu Code erweitert wird, der a zurückgeben möchtevoid
. Dies könnte eine Leere sein, die durch eine Artfinally_noreturn
Makro entsteht.Alles in allem weiß ich nicht, ob ich dieses Zeug jemals selbst benutzen würde, aber es hat Spaß gemacht, damit zu spielen. :) :)
quelle
catch(xxx) {}
Block am Anfang desfinally
Makros einfügen, wobei xxx ein Scheintyp ist, nur um mindestens einen Catch-Block zu haben.catch(...)
, nicht wahr ?xxx
in einem privaten Namespace, der niemals verwendet wird.Ich habe einen Anwendungsfall, in dem ich denke,
finally
dass dies ein durchaus akzeptabler Teil der C ++ 11-Sprache sein sollte, da ich denke, dass es aus Sicht des Flusses einfacher zu lesen ist. Mein Anwendungsfall ist eine Consumer / Producer-Kette von Threads,nullptr
bei der am Ende des Laufs ein Sentinel gesendet wird, um alle Threads herunterzufahren.Wenn C ++ dies unterstützt, soll Ihr Code folgendermaßen aussehen:
Ich denke, dies ist logischer, als Ihre endgültige Deklaration an den Anfang der Schleife zu setzen, da sie nach dem Beenden der Schleife auftritt ... aber das ist Wunschdenken, weil wir es in C ++ nicht tun können. Beachten Sie, dass die Warteschlange
downstream
mit einem anderen Thread verbunden ist, sodass Sie den Sentinel nichtpush(nullptr)
in den Destruktor vondownstream
einfügen können, da er zu diesem Zeitpunkt nicht zerstört werden kann. Er muss am Leben bleiben, bis der andere Thread den empfängtnullptr
.So verwenden Sie eine RAII-Klasse mit Lambda, um dasselbe zu tun:
und so benutzt du es:
quelle
Wie viele Leute angegeben haben, besteht die Lösung darin, C ++ 11-Funktionen zu verwenden, um endgültige Blockierungen zu vermeiden. Eines der Merkmale ist
unique_ptr
.Hier ist Mephanes Antwort, die mit RAII-Mustern geschrieben wurde.
Eine weitere Einführung in die Verwendung von unique_ptr mit C ++ Standard Library-Containern finden Sie hier
quelle
Ich möchte eine Alternative anbieten.
Wenn Sie möchten, dass finally block immer aufgerufen wird, setzen Sie es einfach nach dem letzten catch-Block (was wahrscheinlich sein sollte
catch( ... )
, um eine nicht bekannte Ausnahme abzufangen).Wenn Sie beim Auslösen einer Ausnahme als letztes eine endgültige Blockierung durchführen möchten, können Sie eine boolesche lokale Variable verwenden. Vor dem Ausführen setzen Sie sie auf false und setzen die wahre Zuweisung ganz am Ende des try-Blocks. Nach dem catch-Block wird nach der Variablen gesucht Wert:
quelle
Ich denke auch, dass RIIA kein voll nützlicher Ersatz für die Ausnahmebehandlung und ein endgültiges Problem ist. Übrigens denke ich auch, dass RIIA ein schlechter Name ist. Ich nenne diese Arten von Klassen "Hausmeister" und benutze sie eine Menge. In 95% der Fälle, in denen sie weder Ressourcen initialisieren noch erwerben, wenden sie Änderungen auf einer bestimmten Basis an oder nehmen etwas, das bereits eingerichtet wurde, und stellen sicher, dass es zerstört wird. Dies ist der offizielle Mustername, der vom Internet besessen ist. Ich werde missbraucht, weil ich sogar vorschlage, mein Name könnte besser sein.
Ich halte es einfach nicht für vernünftig zu verlangen, dass für jede komplizierte Einrichtung einer Ad-hoc-Liste von Dingen eine Klasse geschrieben werden muss, um sie zu enthalten, um Komplikationen beim Reinigen zu vermeiden, obwohl mehrere gefangen werden müssen Ausnahmetypen, wenn dabei etwas schief geht. Dies würde zu vielen Ad-hoc-Kursen führen, die sonst einfach nicht notwendig wären.
Ja, es ist in Ordnung für Klassen, die für die Verwaltung einer bestimmten Ressource ausgelegt sind, oder für allgemeine Klassen, die für die Verarbeitung einer Reihe ähnlicher Ressourcen ausgelegt sind. Aber selbst wenn alle beteiligten Dinge solche Wrapper haben, kann die Koordination der Bereinigung nicht nur ein einfacher Aufruf von Destruktoren in umgekehrter Reihenfolge sein.
Ich denke, es ist absolut sinnvoll für C ++, endlich eine zu haben. Ich meine, Herrgott, in den letzten Jahrzehnten wurden so viele Kleinigkeiten darauf geklebt, dass es den Anschein hat, als würden seltsame Leute plötzlich konservativ gegenüber so etwas wie endlich, was sehr nützlich sein könnte und wahrscheinlich nicht annähernd so kompliziert wie einige andere Dinge, die es gewesen sind fügte hinzu (obwohl das nur eine Vermutung von meiner Seite ist.)
quelle
quelle
finally
nicht der Fall ist.