Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 Sie true zurück, wenn erfolgreich, false, wenn keine Verbindungen verfügbar sind
    • release() — erhöhen Sie den verfügbaren Zähler und geben Sie "Connection released (X available)" aus
    • available() — 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 ConnectionPool in 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.

  • main.cpp: Demonstrieren Sie die automatische Bereinigung von RAII durch Gültigkeitsbereiche (Scopes).

    Lesen Sie zwei Eingaben ein:

    1. Pool-Kapazität (Ganzzahl)
    2. Anzahl der anzufordernden Verbindungen (Ganzzahl)

    Erstellen Sie einen ConnectionPool mit der angegebenen Kapazität. Erstellen Sie dann innerhalb eines verschachtelten Bereichs (unter Verwendung geschweifter Klammern) die angeforderte Anzahl von ConnectionGuard-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 available

Mit 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 available

Beachten 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;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung