RAII als Muster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 102 von 104.
RAII (Resource Acquisition Is Initialization) ist mehr als nur ein C++-Idiom – es ist ein leistungsstarkes Entwurfsmuster, das die Ressourcenverwaltung an die Lebensdauer von Objekten bindet. Sie haben RAII bereits bei Smart Pointern gesehen, aber das Muster lässt sich auf jede Ressource anwenden: Dateihandles, Netzwerkverbindungen, Mutexe oder Datenbanktransaktionen.
Die Kernidee ist einfach: Ressourcen im Konstruktor erwerben, sie im Destruktor freigeben. Da C++ garantiert, dass Destruktoren ausgeführt werden, wenn Objekte den Gültigkeitsbereich verlassen, erfolgt die Bereinigung automatisch – selbst wenn Ausnahmen auftreten:
#include <iostream>
#include <fstream>
class FileGuard {
std::ofstream file;
public:
FileGuard(const std::string& filename) : file(filename) {
if (!file.is_open()) {
std::cout << "Failed to open file\n";
}
}
void write(const std::string& text) {
if (file.is_open()) file << text;
}
~FileGuard() {
if (file.is_open()) {
file.close();
std::cout << "File closed automatically\n";
}
}
};
int main() {
{
FileGuard guard("output.txt");
guard.write("Hello RAII");
} // Destruktor wird hier aufgerufen - Datei geschlossen
std::cout << "After scope\n";
}RAII glänzt bei der Verwaltung von Locks in Multithreading-Code. Die std::lock_guard der Standardbibliothek folgt diesem Muster – sie fordert beim Konstruieren einen Mutex an und gibt ihn beim Destruieren wieder frei, wodurch Deadlocks durch vergessene Unlocks verhindert werden.
Bei der Implementierung von RAII-Klassen sollten Sie daran denken, Kopier-/Verschiebeoperationen (Rule of Five) entweder zu löschen oder ordnungsgemäß zu implementieren, um Ressourcenvervielfältigung oder Probleme durch doppelte Freigabe zu vermeiden. RAII verwandelt fehleranfällige manuelle Ressourcenverwaltung in eine sichere, automatische Bereinigung.
Aufgabe
EinfachLassen Sie uns einen Connection Pool Manager unter Verwendung von RAII erstellen, um Datenbankverbindungen sicher zu verwalten. In realen Anwendungen sind Datenbankverbindungen teure Ressourcen, die ordnungsgemäß angefordert und freigegeben werden müssen. Sie werden einen RAII-Wrapper erstellen, der garantiert, dass Verbindungen immer an den Pool zurückgegeben werden, selbst wenn Ausnahmen auftreten oder Codepfade kompliziert werden.
Sie werden Ihren Code über drei Dateien organisieren:
ConnectionPool.h: Erstellen Sie einen einfachen Verbindungspool, der eine begrenzte Anzahl von Verbindungen verwaltet.Ihre
ConnectionPool-Klasse sollte verfolgen, wie viele Verbindungen verfügbar sind (beginnen Sie mit einer Kapazität, die an den Konstruktor übergeben wird). Implementieren Sie:acquire()— wenn eine Verbindung verfügbar ist, verringern Sie den Zähler und geben Sie"Connection acquired (X available)"aus, wobei X der verbleibende Zähler ist; geben Sietruezurück, wenn erfolgreich,false, wenn keine Verbindungen verfügbar sindrelease()— erhöhen Sie den verfügbaren Zähler und geben Sie"Connection released (X available)"ausavailable()— gibt die aktuelle Anzahl der verfügbaren Verbindungen zurück
ConnectionGuard.h: Erstellen Sie den RAII-Wrapper, der eine einzelne Verbindung sicher verwaltet.Ihre
ConnectionGuard-Klasse verkörpert das RAII-Muster. Sie sollte:- Eine Referenz auf einen
ConnectionPoolin ihrem Konstruktor entgegennehmen und versuchen, eine Verbindung zu erwerben - Speichern, ob der Erwerb erfolgreich war
- Eine
isConnected()-Methode bereitstellen, um zu prüfen, ob der Guard eine gültige Verbindung hält - Die Verbindung im Destruktor automatisch an den Pool zurückgeben (nur wenn eine erworben wurde)
- Kopierkonstruktor und Kopierzuweisung löschen, um Ressourcenvervielfältigung zu verhindern (Berücksichtigung der Rule of Five)
Wenn der Destruktor ausgeführt wird und eine Verbindung gehalten wurde, geben Sie
"Guard releasing connection"aus, bevor Sie release auf dem Pool aufrufen.- Eine Referenz auf einen
main.cpp: Demonstrieren Sie die automatische Bereinigung von RAII durch Gültigkeitsbereiche (Scopes).Lesen Sie zwei Eingaben ein:
- Pool-Kapazität (Ganzzahl)
- Anzahl der anzufordernden Verbindungen (Ganzzahl)
Erstellen Sie einen
ConnectionPoolmit der angegebenen Kapazität. Erstellen Sie dann innerhalb eines verschachtelten Bereichs (unter Verwendung geschweifter Klammern) die angeforderte Anzahl vonConnectionGuard-Objekten, die in einem Vektor gespeichert sind. Geben Sie für jeden Guard aus, ob die Verbindung erfolgreich war:- Wenn verbunden:
"Guard N: Connected" - Wenn nicht verbunden:
"Guard N: Failed to connect"
(wobei N bei 1 beginnt)
Nachdem der Bereich endet (Guards werden zerstört), geben Sie
"After scope: X connections available"aus, um den Endzustand des Pools anzuzeigen.
Zum Beispiel mit den Eingaben 2 und 3:
Connection acquired (1 available)
Guard 1: Connected
Connection acquired (0 available)
Guard 2: Connected
Guard 3: Failed to connect
Guard releasing connection
Connection released (1 available)
Guard releasing connection
Connection released (2 available)
After scope: 2 connections availableMit den Eingaben 3 und 2:
Connection acquired (2 available)
Guard 1: Connected
Connection acquired (1 available)
Guard 2: Connected
Guard releasing connection
Connection released (2 available)
Guard releasing connection
Connection released (3 available)
After scope: 3 connections availableBeachten Sie, wie die Verbindungen automatisch freigegeben werden, wenn die Guards den Gültigkeitsbereich verlassen – Sie rufen in Ihrem Hauptcode niemals explizit release auf. Die Destruktoren laufen in umgekehrter Reihenfolge der Konstruktion ab (der letzte Guard wird zuerst zerstört), und jede erworbene Verbindung wird garantiert zurückgegeben. Dies ist die Stärke von RAII: Die Ressourcenbereinigung erfolgt automatisch und zuverlässig, egal wie der Bereich verlassen wird.
Spickzettel
RAII (Resource Acquisition Is Initialization) ist ein Entwurfsmuster, das die Ressourcenverwaltung an die Lebensdauer von Objekten bindet. Ressourcen werden im Konstruktor angefordert und im Destruktor freigegeben.
Da C++ garantiert, dass Destruktoren ausgeführt werden, wenn Objekte ihren Gültigkeitsbereich verlassen, erfolgt die Bereinigung automatisch – selbst wenn Ausnahmen auftreten:
class FileGuard {
std::ofstream file;
public:
FileGuard(const std::string& filename) : file(filename) {
if (!file.is_open()) {
std::cout << "Failed to open file\n";
}
}
void write(const std::string& text) {
if (file.is_open()) file << text;
}
~FileGuard() {
if (file.is_open()) {
file.close();
std::cout << "File closed automatically\n";
}
}
};
int main() {
{
FileGuard guard("output.txt");
guard.write("Hello RAII");
} // Destruktor wird hier aufgerufen - Datei wird automatisch geschlossen
std::cout << "After scope\n";
}RAII ist besonders nützlich für die Verwaltung von Sperren (Locks) in Multithreading-Code. Die std::lock_guard der Standardbibliothek fordert beim Erstellen einen Mutex an und gibt ihn beim Zerstören wieder frei, wodurch Deadlocks durch vergessene Freigaben verhindert werden.
Beim Implementieren von RAII-Klassen sollten Kopier- und Verschiebe operationen (Rule of Five) entweder gelöscht oder ordnungsgemäß implementiert werden, um Ressourcenverdopplung oder Probleme durch doppelte Freigaben zu vermeiden.
Probier es selbst
#include <iostream>
#include <vector>
#include "ConnectionPool.h"
#include "ConnectionGuard.h"
using namespace std;
int main() {
// Eingaben lesen
int capacity;
int numConnections;
cin >> capacity;
cin >> numConnections;
// TODO: Erstelle einen ConnectionPool mit der angegebenen Kapazität
// TODO: Erstelle einen verschachtelten Gültigkeitsbereich (Scope) mit geschweiften Klammern
{
// TODO: Erstelle einen Vektor, um ConnectionGuard-Objekte zu speichern
// Hinweis: Du musst Pointer oder Smart Pointer verwenden, da ConnectionGuard
// einen gelöschten Kopierkonstruktor hat
// TODO: Schleife, um numConnections Guards zu erstellen
// Gib für jeden Guard entweder Folgendes aus:
// "Guard N: Connected" oder "Guard N: Failed to connect"
// wobei N bei 1 beginnt
}
// Guards werden hier zerstört, wenn der Scope endet
// TODO: Gib "After scope: X connections available" aus
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins via CRTPPimpl-IdiomType ErasureEnum-Klassen & starke TypisierungException-Handling in OOPEigene Exception-Hierarchien14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container