Was ist unter Ressourcenakquisition unter Initialisierung (RAII) zu verstehen?
Was ist unter Ressourcenakquisition unter Initialisierung (RAII) zu verstehen?
Antworten:
Es ist ein wirklich schrecklicher Name für ein unglaublich leistungsfähiges Konzept und vielleicht eines der wichtigsten Dinge, die C ++ - Entwickler vermissen, wenn sie in andere Sprachen wechseln. Es gab eine gewisse Bewegung, um zu versuchen, dieses Konzept in Scope-Bound Resource Management umzubenennen , obwohl es sich noch nicht durchgesetzt zu haben scheint.
Wenn wir "Ressource" sagen, meinen wir nicht nur Speicher - es können Datei-Handles, Netzwerk-Sockets, Datenbank-Handles, GDI-Objekte sein ... Kurz gesagt, Dinge, die wir endlich zur Verfügung haben und die wir müssen kontrollieren ihre Verwendung. Der Aspekt "Gültigkeitsbereich gebunden" bedeutet, dass die Lebensdauer des Objekts an den Gültigkeitsbereich einer Variablen gebunden ist. Wenn die Variable den Gültigkeitsbereich verlässt, gibt der Destruktor die Ressource frei. Eine sehr nützliche Eigenschaft davon ist, dass es für eine größere Ausnahmesicherheit sorgt. Vergleichen Sie zum Beispiel Folgendes:
RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation(); // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks
Mit dem RAII
class ManagedResourceHandle {
public:
ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
~ManagedResourceHandle() {delete rawHandle; }
... // omitted operator*, etc
private:
RawResourceHandle* rawHandle;
};
ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();
In diesem letzteren Fall werden die lokalen Variablen zerstört, wenn die Ausnahme ausgelöst und der Stapel abgewickelt wird, wodurch sichergestellt wird, dass unsere Ressource bereinigt wird und nicht ausläuft.
Scope-Bound
die beste Namenswahl ist, da Speicherklassenspezifizierer zusammen mit dem Bereich die Speicherdauer einer Entität bestimmen. Es ist vielleicht eine nützliche Vereinfachung, es auf den Umfang zu beschränken, aber es ist nicht 100% genau
Dies ist eine Programmiersprache, die kurz bedeutet, dass Sie
Dies garantiert, dass alles, was passiert, während die Ressource verwendet wird, irgendwann freigegeben wird (sei es aufgrund einer normalen Rückgabe, einer Zerstörung des enthaltenen Objekts oder einer ausgelösten Ausnahme).
Es ist eine weit verbreitete bewährte Methode in C ++, da es nicht nur ein sicherer Weg ist, mit Ressourcen umzugehen, sondern auch Ihren Code viel sauberer macht, da Sie keinen Fehlerbehandlungscode mit der Hauptfunktionalität mischen müssen.
*
Update: "lokal" kann eine lokale Variable oder eine nicht statische Mitgliedsvariable einer Klasse bedeuten. Im letzteren Fall wird die Mitgliedsvariable mit ihrem Eigentümerobjekt initialisiert und zerstört.
**
Update2: Wie @sbi betonte, kann die Ressource - obwohl sie häufig innerhalb des Konstruktors zugewiesen wird - auch außerhalb zugewiesen und als Parameter übergeben werden.
open()
/ close()
Methoden gibt, um die Ressource zu initialisieren und freizugeben, nur den Konstruktor und den Destruktor, so dass das "Halten" der Ressource nur die Lebensdauer des Objekts ist, egal ob diese Lebensdauer ist behandelt durch den Kontext (Stapel) oder explizit (dynamische Zuordnung)
„RAH“ steht für „Ressourcenerfassung ist Initialisierung“ und ist eigentlich recht eine falsche Bezeichnung, da es nicht Ressource Erwerb (und die Initialisierung eines Objekts) mit betroffen ist, aber die Freigabe der Ressource (durch Zerstörung eines Objekts ).
Aber RAII ist der Name, den wir haben und der bleibt.
Das Kernstück der Redewendung besteht darin, Ressourcen (Speicherblöcke, geöffnete Dateien, entsperrte Mutexe, Sie-Name-it) in lokalen, automatischen Objekten zu kapseln und den Destruktor dieses Objekts die Ressource freizugeben, wenn das Objekt am zerstört wird Ende des Bereichs, zu dem es gehört:
{
raii obj(acquire_resource());
// ...
} // obj's dtor will call release_resource()
Natürlich sind Objekte nicht immer lokale, automatische Objekte. Sie könnten auch Mitglieder einer Klasse sein:
class something {
private:
raii obj_; // will live and die with instances of the class
// ...
};
Wenn solche Objekte den Speicher verwalten, werden sie häufig als "intelligente Zeiger" bezeichnet.
Es gibt viele Variationen davon. In den ersten Codefragmenten stellt sich beispielsweise die Frage, was passieren würde, wenn jemand kopieren wollte obj
. Der einfachste Ausweg wäre, das Kopieren einfach zu verbieten. std::unique_ptr<>
Dies ist ein intelligenter Zeiger, der Teil der Standardbibliothek ist, wie sie im nächsten C ++ - Standard enthalten ist.
Ein weiterer solcher intelligenter Zeiger std::shared_ptr
bietet "gemeinsames Eigentum" an der Ressource (einem dynamisch zugewiesenen Objekt), die er enthält. Das heißt, es kann frei kopiert werden und alle Kopien beziehen sich auf dasselbe Objekt. Der Smart Pointer verfolgt, wie viele Kopien auf dasselbe Objekt verweisen, und löscht es, wenn das letzte zerstört wird.
Eine dritte Variante wird von vorgestelltstd::auto_ptr
Dies implementiert eine Art Verschiebungssemantik: Ein Objekt gehört nur einem Zeiger, und der Versuch, ein Objekt zu kopieren, führt (durch Syntax-Hackery) dazu, dass das Eigentum an dem Objekt auf das Ziel des Kopiervorgangs übertragen wird.
std::auto_ptr
ist veraltete Version von std::unique_ptr
. std::auto_ptr
Art der simulierten Verschiebungssemantik, so weit es in C ++ 98 möglich war, std::unique_ptr
verwendet die neue Verschiebungssemantik von C ++ 11. Eine neue Klasse wurde erstellt, da die Verschiebungssemantik von C ++ 11 expliziter ist ( std::move
außer temporär erforderlich ), während sie für jede Kopie von Nicht-Konstanten standardmäßig verwendet wurde std::auto_ptr
.
Die Lebensdauer eines Objekts wird durch seinen Umfang bestimmt. Manchmal müssen oder müssen wir jedoch ein Objekt erstellen, das unabhängig von dem Bereich lebt, in dem es erstellt wurde. In C ++ wird der Operator new
verwendet, um ein solches Objekt zu erstellen. Und um das Objekt zu zerstören, kann der Bediener delete
verwendet werden. Vom Bediener erstellte Objekte new
werden dynamisch zugewiesen, dh im dynamischen Speicher zugewiesen (auch als Heap oder freier Speicher bezeichnet ). Ein Objekt, das von erstellt wurde, new
bleibt also bestehen, bis es mit explizit zerstört wird delete
.
Einige Fehler, die bei der Verwendung auftreten können new
und delete
sind:
new
diese Option, um ein Objekt zuzuweisen und delete
das Objekt zu vergessen .delete
das Objekt, und verwenden Sie dann den anderen Zeiger.delete
wird zweimal versucht, ein Objekt zu löschen .Im Allgemeinen werden Bereichsvariablen bevorzugt. RAII kann jedoch als Alternative zu new
und verwendet werden delete
, um ein Objekt unabhängig von seinem Umfang zum Leben zu erwecken. Eine solche Technik besteht darin, den Zeiger auf das Objekt zu nehmen, das auf dem Heap zugewiesen wurde, und ihn in einem Handle / Manager-Objekt zu platzieren . Letzterer hat einen Destruktor, der sich um die Zerstörung des Objekts kümmert. Dadurch wird sichergestellt, dass das Objekt für alle Funktionen verfügbar ist, die Zugriff darauf wünschen, und dass das Objekt zerstört wird, wenn die Lebensdauer des Handle-Objekts endet, ohne dass eine explizite Bereinigung erforderlich ist.
Beispiele aus der C ++ - Standardbibliothek, die RAII verwenden, sind std::string
und std::vector
.
Betrachten Sie diesen Code:
void fn(const std::string& str)
{
std::vector<char> vec;
for (auto c : str)
vec.push_back(c);
// do something
}
Wenn Sie einen Vektor erstellen und Elemente darauf verschieben, ist es Ihnen egal, ob Sie solche Elemente zuweisen oder freigeben. Der Vektor verwendet new
, um Speicherplatz für seine Elemente auf dem Heap zuzuweisen und delete
diesen Speicherplatz freizugeben. Als Benutzer von vector interessieren Sie sich nicht für die Implementierungsdetails und vertrauen darauf, dass vector nicht ausläuft. In diesem Fall ist der Vektor das Handle-Objekt seiner Elemente.
Weitere Beispiele aus der Standardbibliothek , dass die Verwendung RAH sind std::shared_ptr
, std::unique_ptr
und std::lock_guard
.
Ein anderer Name für diese Technik ist SBRM , kurz für Scope-Bound Resource Management .
Das Buch C ++ Programming with Design Patterns Revealed beschreibt RAII wie folgt:
Wo
Ressourcen werden als Klassen implementiert, und alle Zeiger sind von Klassenumbrüchen umgeben (was sie zu intelligenten Zeigern macht).
Ressourcen werden durch Aufrufen ihrer Konstruktoren erfasst und implizit (in umgekehrter Reihenfolge des Erwerbs) durch Aufrufen ihrer Destruktoren freigegeben.
Eine RAII-Klasse besteht aus drei Teilen:
RAII steht für "Resource Acquisition is Initialization". Im Teil "Ressourcenbeschaffung" von RAII beginnen Sie etwas, das später beendet werden muss, z.
Der Teil "Ist Initialisierung" bedeutet, dass die Erfassung innerhalb des Konstruktors einer Klasse erfolgt.
Die manuelle Speicherverwaltung ist ein Albtraum, den Programmierer seit der Erfindung des Compilers erfunden haben. Programmiersprachen mit Garbage Collectors erleichtern das Leben, jedoch auf Kosten der Leistung. In diesem Artikel - Beseitigung des Müllsammlers : Der RAII-Weg gibt uns der Toptal-Ingenieur Peter Goodspeed-Niklaus einen Einblick in die Geschichte der Müllsammler und erklärt, wie Vorstellungen von Eigentum und Ausleihe dazu beitragen können, Müllsammler zu beseitigen, ohne ihre Sicherheitsgarantien zu beeinträchtigen.