Könnten Sie C ++ - Entwicklern bitte eine gute Beschreibung geben, was RAII ist, warum es wichtig ist und ob es für andere Sprachen relevant sein könnte oder nicht?
Ich tue ein wenig kennen. Ich glaube, es steht für "Resource Acquisition is Initialization". Dieser Name stimmt jedoch nicht mit meinem (möglicherweise falschen) Verständnis von RAII überein: Ich habe den Eindruck, dass RAII eine Möglichkeit ist, Objekte auf dem Stapel so zu initialisieren, dass die Destruktoren automatisch ausgeführt werden, wenn diese Variablen den Gültigkeitsbereich verlassen aufgerufen werden, wodurch die Ressourcen bereinigt werden.
Warum heißt das nicht "Verwenden des Stapels zum Auslösen der Bereinigung" (UTSTTC :)? Wie kommt man von dort zu "RAII"?
Und wie können Sie etwas auf dem Stapel machen, das die Bereinigung von etwas bewirkt, das auf dem Haufen lebt? Gibt es auch Fälle, in denen Sie RAII nicht verwenden können? Wünschen Sie sich jemals eine Müllabfuhr? Zumindest einen Garbage Collector, den Sie für einige Objekte verwenden könnten, während andere verwaltet werden könnten?
Vielen Dank.
quelle
:(
Ich habe damals den ganzen Thread gelesen und mich nicht einmal als C ++ - Neuling betrachtet!Antworten:
RAII sagt Ihnen, was zu tun ist: Erwerben Sie Ihre Ressource in einem Konstruktor! Ich würde hinzufügen: eine Ressource, ein Konstruktor. UTSTTC ist nur eine Anwendung davon, RAII ist viel mehr.
Ressourcenmanagement ist scheiße. Hier ist Ressource alles, was nach der Verwendung bereinigt werden muss. Studien von Projekten auf vielen Plattformen zeigen, dass die meisten Fehler mit der Ressourcenverwaltung zusammenhängen - und dass dies unter Windows besonders schlimm ist (aufgrund der vielen Arten von Objekten und Zuordnern).
In C ++ ist die Ressourcenverwaltung aufgrund der Kombination von Ausnahmen und Vorlagen (im C ++ - Stil) besonders kompliziert. Für einen Blick unter die Haube siehe Got8 ).
C ++ garantiert, dass der Destruktor genau dann aufgerufen wird, wenn der Konstruktor erfolgreich war. Darauf aufbauend kann RAII viele schlimme Probleme lösen, die dem durchschnittlichen Programmierer möglicherweise gar nicht bewusst sind. Hier sind einige Beispiele, die über "Meine lokalen Variablen werden bei jeder Rückkehr zerstört" hinausgehen.
Beginnen wir mit einer zu simplen
FileHandle
Klasse, die RAII einsetzt:Wenn die Konstruktion fehlschlägt (mit einer Ausnahme), wird keine andere Elementfunktion - nicht einmal der Destruktor - aufgerufen.
RAII vermeidet die Verwendung von Objekten in einem ungültigen Zustand. Es macht das Leben schon einfacher, bevor wir das Objekt überhaupt benutzen.
Schauen wir uns nun temporäre Objekte an:
Es sind drei Fehlerfälle zu behandeln: Es kann keine Datei geöffnet werden, es kann nur eine Datei geöffnet werden, beide Dateien können geöffnet werden, aber das Kopieren der Dateien ist fehlgeschlagen. In einer Nicht-RAII-Implementierung
Foo
müssten alle drei Fälle explizit behandelt werden.RAII gibt Ressourcen frei, die erworben wurden, auch wenn mehrere Ressourcen innerhalb einer Anweisung erfasst wurden.
Lassen Sie uns nun einige Objekte zusammenfassen:
Der Konstruktor von
Logger
wird fehlschlagen, wennoriginal
der Konstruktor fehlschlägt (weilfilename1
nicht geöffnet werden konnte),duplex
der Konstruktor fehlschlägt (weilfilename2
nicht geöffnet werden konnte) oder das Schreiben in die Dateien imLogger
Konstruktorkörper fehlschlägt. In jedem dieser Fälle wirdLogger
der Destruktor nicht aufgerufen. Daher können wir uns nicht aufLogger
den Destruktor verlassen, um die Dateien freizugeben. Wenn es erstelltoriginal
wurde, wird sein Destruktor während der Bereinigung desLogger
Konstruktors aufgerufen .RAII vereinfacht die Bereinigung nach Teilkonstruktion.
Negative Punkte:
Negative Punkte? Alle Probleme können mit RAII und Smart Pointern gelöst werden ;-)
RAII ist manchmal unhandlich, wenn Sie eine verzögerte Erfassung benötigen und aggregierte Objekte auf den Heap verschieben.
Stellen Sie sich vor, der Logger braucht a
SetTargetFile(const char* target)
. In diesem Fall muss sich das Handle, zu dem noch MitgliedLogger
gehören muss, auf dem Heap befinden (z. B. in einem intelligenten Zeiger, um die Zerstörung des Handles entsprechend auszulösen.)Ich habe mir nie wirklich eine Müllabfuhr gewünscht. Wenn ich C # mache, fühle ich manchmal einen Moment der Glückseligkeit, den ich einfach nicht interessieren muss, aber viel mehr vermisse ich all die coolen Spielzeuge, die durch deterministische Zerstörung entstehen können. (mit
IDisposable
einfach schneidet es nicht.)Ich hatte eine besonders komplexe Struktur, die möglicherweise von GC profitiert hat, bei der "einfache" intelligente Zeiger Zirkelverweise über mehrere Klassen verursachen würden. Wir haben uns durch sorgfältiges Abwägen von starken und schwachen Zeigern durcheinander gebracht, aber jedes Mal, wenn wir etwas ändern wollen, müssen wir ein großes Beziehungsdiagramm studieren. GC war vielleicht besser, aber einige der Komponenten enthielten Ressourcen, die so schnell wie möglich veröffentlicht werden sollten.
Ein Hinweis zum FileHandle-Beispiel: Es sollte nicht vollständig sein, sondern nur ein Beispiel - aber es stellte sich als falsch heraus. Vielen Dank an Johannes Schaub für den Hinweis und FredOverflow für die Umwandlung in eine korrekte C ++ 0x-Lösung. Im Laufe der Zeit habe ich mich mit dem hier dokumentierten Ansatz abgefunden .
quelle
Foo
gehörtBar
undBoz
mutiert, ...Es gibt ausgezeichnete Antworten, also füge ich nur einige vergessene Dinge hinzu.
0. Bei RAII geht es um Bereiche
Bei RAII geht es um beides:
Andere haben bereits darauf geantwortet, deshalb werde ich nicht näher darauf eingehen.
1. Beim Codieren in Java oder C # verwenden Sie bereits RAII ...
Wie Monsieur Jourdain es mit Prosa tat, verwenden C # und sogar Java-Leute bereits RAII, aber auf versteckte Weise. Zum Beispiel der folgende Java-Code (der in C # durch Ersetzen
synchronized
durch gleich geschrieben wirdlock
):... verwendet bereits RAII: Die Mutex-Erfassung erfolgt im Schlüsselwort (
synchronized
oderlock
), und die Aufhebung der Erfassung erfolgt beim Beenden des Bereichs.Es ist so natürlich in seiner Notation, dass es selbst für Leute, die noch nie von RAII gehört haben, fast keiner Erklärung bedarf.
Der Vorteil von C ++ gegenüber Java und C # besteht darin, dass mit RAII alles möglich ist. Zum Beispiel gibt es kein direktes eingebautes Äquivalent von
synchronized
oderlock
in C ++, aber wir können sie trotzdem haben.In C ++ würde es geschrieben werden:
die leicht auf Java / C # Weise geschrieben werden kann (unter Verwendung von C ++ - Makros):
2. RAII haben alternative Verwendungszwecke
Sie wissen, wann der Konstruktor aufgerufen wird (bei der Objektdeklaration), und Sie wissen, wann der entsprechende Destruktor aufgerufen wird (am Ende des Bereichs), sodass Sie fast magischen Code mit nur einer Zeile schreiben können. Willkommen im C ++ - Wunderland (zumindest aus Sicht eines C ++ - Entwicklers).
Zum Beispiel können Sie ein Zählerobjekt schreiben (ich lasse das als Übung) und es verwenden, indem Sie einfach seine Variable deklarieren, wie das obige Sperrobjekt verwendet wurde:
was natürlich wieder auf Java / C # Weise mit einem Makro geschrieben werden kann:
3. Warum fehlt C ++
finally
?Die
finally
Klausel wird in C # / Java verwendet, um die Ressourcenentsorgung im Falle eines Bereichsausgangs zu handhaben (entweder durch einereturn
oder eine ausgelöste Ausnahme).Kluge Spezifikationsleser werden bemerkt haben, dass C ++ keine finally-Klausel hat. Und dies ist kein Fehler, da C ++ ihn nicht benötigt, da RAII bereits die Ressourcenentsorgung übernimmt. (Und glauben Sie mir, das Schreiben eines C ++ - Destruktors ist um ein Vielfaches einfacher als das Schreiben der richtigen Java finally-Klausel oder sogar der korrekten Dispose-Methode eines C #).
Trotzdem
finally
wäre eine Klausel manchmal cool. Können wir das in C ++ machen? Ja wir können! Und wieder mit einer alternativen Verwendung von RAII.Fazit: RAII ist in C ++ mehr als eine Philosophie: Es ist C ++
Wenn Sie ein gewisses Maß an Erfahrung in C ++ erreicht haben, denken Sie an RAII , an die automatisierte Ausführung von Construtoren und Destruktoren .
Sie fangen an, in Bereichen zu denken , und die Zeichen
{
und}
werden zu einem der wichtigsten in Ihrem Code.Und fast alles passt in Bezug auf RAII: Ausnahmesicherheit, Mutexe, Datenbankverbindungen, Datenbankanforderungen, Serververbindung, Uhren, Betriebssystemhandles usw. und nicht zuletzt Speicher.
Der Datenbankteil ist nicht zu vernachlässigen, da Sie, wenn Sie die Zahlung des Preises akzeptieren, sogar in einem " Transaktionsprogrammierungsstil " schreiben können , indem Sie Zeilen und Codezeilen ausführen, bis Sie am Ende entscheiden, ob Sie alle Änderungen übernehmen möchten oder, falls nicht möglich, alle Änderungen zurücksetzen lassen (solange jede Zeile mindestens die Garantie für starke Ausnahmen erfüllt). ( Informationen zur Transaktionsprogrammierung finden Sie im zweiten Teil dieses Herb's Sutter-Artikels .)
Und wie ein Puzzle passt alles.
RAII ist so sehr Teil von C ++, dass C ++ ohne C ++ nicht C ++ sein könnte.
Dies erklärt, warum erfahrene C ++ - Entwickler so verliebt in RAII sind und warum RAII das erste ist, wonach sie suchen, wenn sie eine andere Sprache ausprobieren.
Und es erklärt, warum der Garbage Collector, obwohl er an sich schon ein großartiges Stück Technologie ist, aus Sicht eines C ++ - Entwicklers nicht so beeindruckend ist:
quelle
Bitte sehen Sie:
Verwenden, kennen oder verstehen Programmierer anderer Sprachen als C ++ RAII?
RAII und Smart Pointer in C ++
Unterstützt C ++ "finally" -Blöcke? (Und was ist das für ein 'RAII', von dem ich immer wieder höre?)
RAII vs. Ausnahmen
etc..
quelle
RAII verwendet die Semantik von C ++ - Destruktoren, um Ressourcen zu verwalten. Betrachten Sie beispielsweise einen intelligenten Zeiger. Sie haben einen parametrisierten Konstruktor des Zeigers, der diesen Zeiger mit der Adresse des Objekts initialisiert. Sie weisen dem Stapel einen Zeiger zu:
Wenn der Smart Pointer den Gültigkeitsbereich verlässt, löscht der Destruktor der Zeigerklasse das verbundene Objekt. Der Zeiger wird dem Stapel zugewiesen und das Objekt dem Heap zugewiesen.
Es gibt bestimmte Fälle, in denen RAII nicht hilft. Wenn Sie beispielsweise intelligente Zeiger mit Referenzzählung (wie boost :: shared_ptr) verwenden und eine grafische Struktur mit einem Zyklus erstellen, besteht die Gefahr, dass ein Speicherverlust auftritt, da die Objekte in einem Zyklus verhindern, dass sie sich gegenseitig freigeben. Müllabfuhr würde dagegen helfen.
quelle
Ich möchte es etwas stärker ausdrücken als frühere Antworten.
RAII, Ressourcenerfassung ist Initialisierung bedeutet, dass alle erfassten Ressourcen im Rahmen der Initialisierung eines Objekts erfasst werden sollten. Dies verbietet den Erwerb von "nackten" Ressourcen. Das Grundprinzip ist, dass die Bereinigung in C ++ auf Objektbasis und nicht auf Funktionsaufrufbasis funktioniert. Daher sollte die gesamte Bereinigung von Objekten und nicht von Funktionsaufrufen durchgeführt werden. In diesem Sinne ist C ++ objektorientierter als zB Java. Die Java-Bereinigung basiert auf Funktionsaufrufen in
finally
Klauseln.quelle
Ich stimme mit Cpitis überein. Aber ich möchte hinzufügen, dass die Ressourcen alles sein können, nicht nur Speicher. Die Ressource kann eine Datei, ein kritischer Abschnitt, ein Thread oder eine Datenbankverbindung sein.
Dies wird als Ressourcenerfassung ist Initialisierung bezeichnet, da die Ressource erfasst wird, wenn das Objekt erstellt wird, das die Ressource steuert. Wenn der Konstruktor fehlgeschlagen ist (dh aufgrund einer Ausnahme), wird die Ressource nicht erfasst. Sobald das Objekt den Gültigkeitsbereich verlässt, wird die Ressource freigegeben. c ++ garantiert, dass alle Objekte auf dem Stapel, die erfolgreich erstellt wurden, zerstört werden (dies schließt Konstruktoren von Basisklassen und Mitgliedern ein, selbst wenn der Superklassenkonstruktor ausfällt).
Der Grund für RAII besteht darin, die Ausnahme für die Ressourcenbeschaffung sicher zu machen. Dass alle erworbenen Ressourcen ordnungsgemäß freigegeben werden, unabhängig davon, wo eine Ausnahme auftritt. Dies hängt jedoch von der Qualität der Klasse ab, die die Ressource erwirbt (dies muss ausnahmesicher sein und ist schwierig).
quelle
Das Problem bei der Speicherbereinigung besteht darin, dass Sie die deterministische Zerstörung verlieren, die für RAII von entscheidender Bedeutung ist. Sobald eine Variable den Gültigkeitsbereich verlässt, liegt es am Garbage Collector, wann das Objekt zurückgefordert wird. Die vom Objekt gehaltene Ressource bleibt so lange erhalten, bis der Destruktor aufgerufen wird.
quelle
RAII stammt von Resource Allocation Is Initialization. Grundsätzlich bedeutet dies, dass das konstruierte Objekt nach Abschluss der Ausführung durch einen Konstruktor vollständig initialisiert und einsatzbereit ist. Dies bedeutet auch, dass der Destruktor alle Ressourcen (z. B. Speicher, Betriebssystemressourcen) freigibt, die dem Objekt gehören.
Im Vergleich zu durch Müll gesammelten Sprachen / Technologien (z. B. Java, .NET) ermöglicht C ++ die vollständige Kontrolle über die Lebensdauer eines Objekts. Bei einem Stapel-zugewiesenen Objekt wissen Sie, wann der Destruktor des Objekts aufgerufen wird (wenn die Ausführung den Bereich verlässt), was im Fall einer Speicherbereinigung nicht wirklich gesteuert wird. Selbst wenn Sie in C ++ intelligente Zeiger verwenden (z. B. boost :: shared_ptr), wissen Sie, dass der Destruktor dieses Objekts aufgerufen wird, wenn kein Verweis auf das spitze Objekt vorhanden ist.
quelle
Wenn eine Instanz von int_buffer existiert, muss sie eine Größe haben und weist den erforderlichen Speicher zu. Wenn es außerhalb des Gültigkeitsbereichs liegt, wird sein Destruktor aufgerufen. Dies ist sehr nützlich für Dinge wie Synchronisationsobjekte. Erwägen
Nein nicht wirklich.
Noch nie. Die Speicherbereinigung löst nur einen sehr kleinen Teil der dynamischen Ressourcenverwaltung.
quelle
Hier gibt es bereits viele gute Antworten, aber ich möchte nur hinzufügen:
Eine einfache Erklärung für RAII ist, dass in C ++ ein auf dem Stapel zugewiesenes Objekt zerstört wird, wenn es den Gültigkeitsbereich verlässt. Das heißt, ein Objektzerstörer wird aufgerufen und kann alle erforderlichen Bereinigungen durchführen.
Das heißt, wenn ein Objekt ohne "neu" erstellt wird, ist kein "Löschen" erforderlich. Und dies ist auch die Idee hinter "intelligenten Zeigern" - sie befinden sich auf dem Stapel und umschließen im Wesentlichen ein Heap-basiertes Objekt.
quelle
RAII ist eine Abkürzung für Resource Acquisition Is Initialization.
Diese Technik ist in C ++ sehr einzigartig, da sie sowohl Konstruktoren als auch Destruktoren unterstützt und fast automatisch die Konstruktoren, die den übergebenen Argumenten entsprechen, oder im schlimmsten Fall den Standardkonstruktor & destructors nennt, wenn die angegebene Explizität andernfalls die Standardkonstruktion genannt wird Das vom C ++ - Compiler hinzugefügte wird aufgerufen, wenn Sie keinen Destruktor explizit für eine C ++ - Klasse geschrieben haben. Dies geschieht nur für C ++ - Objekte, die automatisch verwaltet werden - dh nicht den freien Speicher verwenden (Speicher zugewiesen / freigegeben mit neuen, neuen [] / Löschen, Löschen [] C ++ - Operatoren).
Die RAII-Technik verwendet diese automatisch verwaltete Objektfunktion, um die Objekte zu verarbeiten, die auf dem Heap / Free-Store erstellt werden, indem explizit mit new / new [] nach mehr Speicher gefragt wird, der durch Aufrufen von delete / delete [] explizit zerstört werden sollte. . Die Klasse des automatisch verwalteten Objekts umschließt dieses andere Objekt, das im Heap- / Free-Store-Speicher erstellt wird. Wenn der Konstruktor des automatisch verwalteten Objekts ausgeführt wird, wird das umschlossene Objekt im Heap- / Free-Store-Speicher erstellt. Wenn das Handle des automatisch verwalteten Objekts den Gültigkeitsbereich verlässt, wird der Destruktor des automatisch verwalteten Objekts automatisch aufgerufen, in dem das umschlossene Objekt ausgeführt wird Objekt wird durch Löschen zerstört. Wenn Sie mit OOP-Konzepten solche Objekte in eine andere Klasse im privaten Bereich einschließen, haben Sie keinen Zugriff auf die umschlossenen Klassenmitglieder & Methoden & Dies ist der Grund, warum intelligente Zeiger (auch als Handle-Klassen bezeichnet) entwickelt wurden. Diese intelligenten Zeiger setzen das umschlossene Objekt als typisiertes Objekt der Außenwelt und dort frei, indem sie das Aufrufen aller Mitglieder / Methoden ermöglichen, aus denen das exponierte Speicherobjekt besteht. Beachten Sie, dass intelligente Zeiger je nach Bedarf unterschiedliche Geschmacksrichtungen haben. Weitere Informationen hierzu finden Sie in der modernen C ++ - Programmierung von Andrei Alexandrescu oder in der Implementierung / Dokumentation der shared_ptr.hpp-Bibliothek (www.boostorg). Ich hoffe, dies hilft Ihnen, RAII zu verstehen. Weitere Informationen hierzu finden Sie in der modernen C ++ - Programmierung von Andrei Alexandrescu oder in der Implementierung / Dokumentation der shared_ptr.hpp-Bibliothek (www.boostorg). Ich hoffe, dies hilft Ihnen, RAII zu verstehen. Weitere Informationen hierzu finden Sie in der modernen C ++ - Programmierung von Andrei Alexandrescu oder in der Implementierung / Dokumentation der shared_ptr.hpp-Bibliothek (www.boostorg). Ich hoffe, dies hilft Ihnen, RAII zu verstehen.
quelle