Die Bedeutung des Begriffs und des Konzepts verstehen - RAII (Resource Acquisition is Initialization)


110

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.


27
UTSTTC? Ich mag das! Es ist viel intuitiver als RAII. RAII ist schlecht benannt, ich bezweifle, dass ein C ++ - Programmierer dies bestreiten würde. Aber es ist nicht leicht zu ändern. ;)
Jalf

10
Hier ist Stroustrups Ansicht zu diesem Thema: groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi

3
@sbi: Wie auch immer, +1 auf deinen Kommentar nur für die historische Forschung. Ich glaube, dass es interessant genug ist, den Standpunkt des Autors (B. Stroustrup) zum Namen eines Konzepts (RAII) zu haben, um eine eigene Antwort zu haben.
Paercebal

1
@paercebal: Historische Forschung? Jetzt hast du mich sehr alt gemacht. :(Ich habe damals den ganzen Thread gelesen und mich nicht einmal als C ++ - Neuling betrachtet!
sbi

3
+1, ich wollte gerade die gleiche Frage stellen, froh, dass ich nicht der einzige bin, der das Konzept versteht, aber keinen Sinn für den Namen hat. Es scheint, als hätte es RAOI - Resource Acquisition On Initialization heißen sollen.
Laurent

Antworten:


132

Warum heißt das nicht "Verwenden des Stapels zum Auslösen der Bereinigung" (UTSTTC :)?

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 FileHandleKlasse, die RAII einsetzt:

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

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:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

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 Foomü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:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

Der Konstruktor von Loggerwird fehlschlagen, wenn originalder Konstruktor fehlschlägt (weil filename1nicht geöffnet werden konnte), duplexder Konstruktor fehlschlägt (weil filename2nicht geöffnet werden konnte) oder das Schreiben in die Dateien im LoggerKonstruktorkörper fehlschlägt. In jedem dieser Fälle wird Loggerder Destruktor nicht aufgerufen. Daher können wir uns nicht auf Loggerden Destruktor verlassen, um die Dateien freizugeben. Wenn es erstellt originalwurde, wird sein Destruktor während der Bereinigung des LoggerKonstruktors 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 Mitglied Loggergehö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 IDisposableeinfach 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 .


1
+1 Für den Hinweis, dass GC und ASAP nicht ineinander greifen. Tut nicht oft weh, aber wenn doch, ist es nicht einfach zu diagnostizieren: /
Matthieu M.

10
Insbesondere ein Satz, den ich bei früheren Lesungen übersehen habe. Sie sagten, dass "RAII" Ihnen sagt: "Erwerben Sie Ihre Ressourcen in Konstruktoren." Das macht Sinn und ist fast eine wörtliche Umschreibung von "RAII". Jetzt verstehe ich es noch besser (ich würde dich wieder wählen, wenn ich könnte :)
Charlie Flowers

2
Ein Hauptvorteil von GC besteht darin, dass ein Speicherzuweisungsframework die Erstellung von baumelnden Referenzen verhindern kann, wenn kein "unsicherer" Code vorhanden ist (wenn "unsicherer" Code zulässig ist, kann das Framework natürlich nichts verhindern). GC ist RAII auch häufig überlegen, wenn es sich um gemeinsam genutzte unveränderliche Objekte wie Zeichenfolgen handelt, die häufig keinen eindeutigen Eigentümer haben und keine Bereinigung erfordern. Es ist bedauerlich, dass mehr Frameworks nicht versuchen, GC und RAII zu kombinieren, da die meisten Anwendungen eine Mischung aus unveränderlichen Objekten (wo GC am besten wäre) und Objekten haben, die bereinigt werden müssen (wo RAII am besten ist).
Supercat

@supercat: Ich mag GC im Allgemeinen - aber es funktioniert nur für Ressourcen, die der GC "versteht". Beispielsweise kennt der .NET GC die Kosten für COM-Objekte nicht. Wenn Sie sie einfach in einer Schleife erstellen und zerstören, wird die Anwendung in Bezug auf Adressraum oder virtuellen Speicher - was auch immer zuerst eintritt - problemlos in den Boden geraten, ohne überhaupt daran zu denken, einen GC durchzuführen. --- Außerdem vermisse ich selbst in einer perfekt GC-fähigen Umgebung immer noch die Kraft der deterministischen Zerstörung: Sie können das gleiche Muster auf andere Artefakte anwenden, z. B. das Anzeigen von UI-Elementen unter bestimmten Bedingungen.
Peterchen

@peterchen: Eine Sache, die meiner Meinung nach in vielen OOP-bezogenen Überlegungen fehlt, ist das Konzept des Objektbesitzes. Das Verfolgen des Eigentums ist häufig eindeutig für Objekte mit Ressourcen erforderlich, ist jedoch häufig auch für veränderbare Objekte ohne Ressourcen erforderlich. Im Allgemeinen sollten Objekte ihren veränderlichen Zustand entweder in Verweisen auf möglicherweise gemeinsam genutzte unveränderliche Objekte oder in veränderlichen Objekten, deren ausschließlicher Eigentümer sie sind, einkapseln. Solch ein exklusives Eigentum impliziert nicht notwendigerweise einen exklusiven Schreibzugriff, aber wenn es Foogehört Barund Bozmutiert, ...
Supercat

42

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:

  1. Erfassen einer Ressource (unabhängig von der Ressource) im Konstruktor und Aufheben der Erfassung im Destruktor.
  2. Der Konstruktor wird ausgeführt, wenn die Variable deklariert wird, und der Destruktor wird automatisch ausgeführt, wenn die Variable den Gültigkeitsbereich verlässt.

Andere haben bereits darauf geantwortet, deshalb werde ich nicht näher darauf eingehen.

1. Beim Codieren in Java oder C # verwenden Sie bereits RAII ...

MONSIEUR JOURDAIN: Was! Wenn ich sage: "Nicole, bring mir meine Hausschuhe und gib mir meinen Schlummertrunk", ist das Prosa?

PHILOSOPHY MASTER: Ja, Sir.

MONSIEUR JOURDAIN: Seit mehr als vierzig Jahren spreche ich Prosa, ohne etwas darüber zu wissen, und ich bin Ihnen sehr dankbar, dass Sie mir das beigebracht haben.

- Molière: Der Gentleman der Mittelklasse, Akt 2, Szene 4

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 synchronizeddurch gleich geschrieben wird lock):

void foo()
{
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

... verwendet bereits RAII: Die Mutex-Erfassung erfolgt im Schlüsselwort ( synchronizedoder lock), 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 synchronizedoder lockin C ++, aber wir können sie trotzdem haben.

In C ++ würde es geschrieben werden:

void foo()
{
   // etc.

   {
      Lock lock(someObject) ; // lock is an object of type Lock whose
                              // constructor acquires a mutex on
                              // someObject and whose destructor will
                              // un-acquire it 

      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

die leicht auf Java / C # Weise geschrieben werden kann (unter Verwendung von C ++ - Makros):

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. RAII haben alternative Verwendungszwecke

WHITE RABBIT: [singend] Ich bin spät dran / ich bin spät dran / Für ein sehr wichtiges Date. / Keine Zeit "Hallo" zu sagen. / Auf Wiedersehen. / Ich bin spät, ich bin spät, ich bin spät.

- Alice im Wunderland (Disney-Version, 1951)

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:

void foo()
{
   double timeElapsed = 0 ;

   {
      Counter counter(timeElapsed) ;
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

was natürlich wieder auf Java / C # Weise mit einem Makro geschrieben werden kann:

void foo()
{
   double timeElapsed = 0 ;

   COUNTER(timeElapsed)
   {
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

3. Warum fehlt C ++ finally?

[SCHREIEN] Es ist der letzte Countdown!

- Europa: Der letzte Countdown (Entschuldigung, ich hatte hier keine Zitate mehr ... :-)

Die finallyKlausel wird in C # / Java verwendet, um die Ressourcenentsorgung im Falle eines Bereichsausgangs zu handhaben (entweder durch eine returnoder 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 finallywä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 ++

RAII? DAS IST C ++ !!!

- Der empörte Kommentar des C ++ - Entwicklers, schamlos kopiert von einem obskuren Sparta-König und seinen 300 Freunden

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:

  • RAII behandelt bereits die meisten Fälle, die von einem GC bearbeitet werden
  • Ein GC befasst sich besser als RAII mit Zirkelverweisen auf rein verwaltete Objekte (gemindert durch die intelligente Verwendung schwacher Zeiger).
  • Ein GC ist jedoch auf den Speicher beschränkt, während RAII jede Art von Ressource verarbeiten kann.
  • Wie oben beschrieben, kann RAII viel, viel mehr ...

Ein Java-Fan: Ich würde sagen, dass GC viel nützlicher ist als RAII, da es den gesamten Speicher verwaltet und Sie von vielen potenziellen Fehlern befreit. Mit GC können Sie Zirkelreferenzen erstellen, Referenzen zurückgeben und speichern, und es ist schwierig, Fehler zu machen (das Speichern einer Referenz auf ein vermeintlich kurzlebiges Objekt verlängert die Lebensdauer, was eine Art Speicherverlust darstellt, aber das ist das einzige Problem). . Der Umgang mit Ressourcen mit GC funktioniert nicht, aber die meisten Ressourcen in einer Anwendung haben einen trivialen Live-Zyklus, und die wenigen verbleibenden sind keine große Sache. Ich wünschte, wir könnten sowohl GC als auch RAII haben, aber das scheint unmöglich zu sein.
Maaartinus

16

1
Einige davon stimmen mit meiner Frage überein, aber bei einer Suche wurden sie weder angezeigt noch die Liste "Verwandte Fragen", die nach Eingabe einer neuen Frage angezeigt wird. Danke für die Links.
Charlie Flowers

1
@Charlie: Die eingebaute Suche ist in gewisser Hinsicht sehr schwach. Die Verwendung der Tag-Syntax ("[Thema]") ist sehr hilfreich, und viele Leute verwenden Google ...
dmckee --- Ex-Moderator Kätzchen

10

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:

SmartPointer pointer( new ObjectClass() );

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.


2
Also sollte es UCDSTMR heißen :)
Daniel Daranas

Bei einem zweiten Gedanken halte ich UDSTMR für angemessener. Die Sprache (C ++) ist angegeben, daher wird der Buchstabe "C" im Akronym nicht benötigt. UDSTMR steht für Verwenden der Destruktorsemantik zum Verwalten von Ressourcen.
Daniel Daranas

9

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 finallyKlauseln.


Gute Antwort. Und "Initialisierung eines Objekts" bedeutet "Konstruktoren", ja?
Charlie Flowers

@ Charlie: Ja, besonders in diesem Fall.
MSalters

8

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).


Ausgezeichnet, danke, dass Sie die Gründe für den Namen erklärt haben. So wie ich es verstehe, könnten Sie RAII wie folgt umschreiben: "Erwerben Sie niemals eine Ressource durch einen anderen Mechanismus als die (konstruktorbasierte) Initialisierung." Ja?
Charlie Flowers

Ja, dies ist meine Richtlinie, ich bin jedoch sehr vorsichtig beim Schreiben meiner eigenen RAII-Klassen, da diese ausnahmesicher sein müssen. Wenn ich sie schreibe, versuche ich, die Ausnahmesicherheit zu gewährleisten, indem ich andere von Experten geschriebene RAII-Klassen wieder verwende.
iain

Ich fand sie nicht schwer zu schreiben. Wenn Ihre Klassen richtig klein genug sind, sind sie überhaupt nicht schwer.
Rob K

7

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.


4
Das Problem ist nicht nur Determinismus. Das eigentliche Problem ist, dass Finalizer (Java-Benennung) der GC im Weg stehen. GC ist effizient, weil es die toten Objekte nicht zurückruft, sondern sie in Vergessenheit gerät. GCs müssen Objekte mit Finalisierern auf andere Weise verfolgen, um sicherzustellen, dass sie
David Rodríguez - Dribeas genannt werden.

1
außer in java / c # würden Sie wahrscheinlich eher in einem finally-Block als in einem Finalizer aufräumen.
jk.

4

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.


3

Und wie können Sie etwas auf dem Stapel machen, das die Bereinigung von etwas bewirkt, das auf dem Haufen lebt?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

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

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

Gibt es auch Fälle, in denen Sie RAII nicht verwenden können?

Nein nicht wirklich.

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?

Noch nie. Die Speicherbereinigung löst nur einen sehr kleinen Teil der dynamischen Ressourcenverwaltung.


Ich habe Java und C # nur sehr wenig verwendet, daher habe ich es nie verpasst, aber GC hat meinen Stil in Bezug auf die Ressourcenverwaltung, wenn ich sie verwenden musste, sicherlich eingeschränkt, da ich RAII nicht verwenden konnte.
Rob K

1
Ich habe viel C # verwendet und stimme Ihnen zu 100% zu. Tatsächlich betrachte ich einen nicht deterministischen GC als eine Haftung in einer Sprache.
Nemanja Trifunovic

2

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.


1
Nein, das tun sie nicht. Aber haben Sie einen guten Grund, jemals einen intelligenten Zeiger auf dem Haufen zu erstellen? Der Smart Pointer war übrigens nur ein Beispiel dafür, wo RAII nützlich sein kann.
E Dominique

1
Vielleicht ist meine Verwendung von "Stapel" gegen "Haufen" etwas schlampig - mit einem Objekt auf "dem Stapel" meinte ich jedes lokale Objekt. Es kann natürlich ein Teil eines Objekts sein, z. B. auf dem Haufen. Mit "Erstellen eines intelligenten Zeigers auf dem Heap" wollte ich new / delete für den intelligenten Zeiger selbst verwenden.
E Dominique

1

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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.