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. Du hast RAII bereits anhand von Smart-Pointern gesehen, aber das Muster lässt sich auf jede Ressource anwenden: Dateihandles, Netzwerk-connections, Mutexes oder Datenbanktransaktionen.

Die Grundidee ist einfach: acquire Ressourcen im constructor, sie im Destruktor freigeben. Da C++ garantiert, dass Destruktoren ausgeführt werden, wenn Objekte ihren Gültigkeitsbereich verlassen, geschieht die Bereinigung automatically – 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 hier aufgerufen - Datei geschlossen
    
    std::cout << "After scope\n";
}

RAII glänzt bei der Verwaltung von Sperren in mehrthreadigem Code. Der std::lock_guard der Standardbibliothek folgt diesem Muster – er erwirbt beim Erstellen einen Mutex und gibt ihn beim Zerstören frei, wodurch Deadlocks aufgrund vergessener Entsperrungen verhindert werden.

Denke bei der Implementierung von RAII-Klassen daran, entweder delete zu verwenden oder die copy/move-Operationen ordnungsgemäß zu implementieren (Rule of Five), um Probleme durch Ressourcenduplizierung oder doppelte Freigabe zu verhindern. RAII verwandelt die fehleranfällige manuelle Ressourcenverwaltung in eine sichere, automatische Bereinigung.

challenge icon

Aufgabe

Einfach

Erstellen wir einen Connection Pool Manager unter Verwendung von RAII, um Datenbankverbindungen sicher zu verwalten. In realen Anwendungen sind Datenbankverbindungen kostspielige Ressourcen, die ordnungsgemäß erworben und freigegeben werden müssen. Du erstellst einen RAII-Wrapper, der garantiert, dass Verbindungen immer an den Pool zurückgegeben werden, selbst wenn Ausnahmen auftreten oder Codepfade kompliziert werden.

Du organisierst deinen Code auf drei Dateien:

  • ConnectionPool.h: Create einen einfachen Verbindungspool, der eine begrenzte Anzahl von connections verwaltet.

    Deine ConnectionPool-class sollte verfolgen, wie viele connections available sind (beginnend mit einer capacity, die an den constructor übergeben wird). Implementiere:

    • acquire() — wenn eine connection available ist, verringere den count und gib Connection acquired (X available) aus, wobei X der verbleibende count ist; gib true zurück, wenn der Vorgang erfolgreich war, und false, wenn keine connections available sind
    • release() — erhöhe den available count und gib Connection released (X available) aus
    • available() — gibt die aktuelle Anzahl der available connections zurück
  • ConnectionGuard.h: Erstelle den RAII-Wrapper, der eine einzelne connection sicher verwaltet.

    Deine ConnectionGuard-class verkörpert das RAII-Muster. Sie sollte:

    • in ihrem constructor eine Referenz auf einen ConnectionPool übernehmen und versuchen, eine connection zu erwerben
    • speichern, ob die acquisition erfolgreich war
    • eine isConnected()-Methode bereitstellen, um zu prüfen, ob der Guard eine gültige connection hält
    • die connection im destructor automatisch an den Pool zurückgeben (nur wenn eine erworben wurde)
    • copy constructor und copy assignment löschen, um die Duplizierung von Ressourcen zu verhindern (Rule-of-Five-Überlegung)

    Wenn der destructor ausgeführt wird und eine connection gehalten wurde, gib Guard releasing connection aus, bevor release für den Pool aufgerufen wird.

  • main.cpp: Demonstriere die automatische Bereinigung durch RAII mithilfe von Gültigkeitsbereichen.

    Lies zwei Eingaben ein:

    1. Pool capacity (Ganzzahl)
    2. Anzahl der anzufordernden connections (Ganzzahl)

    Create einen ConnectionPool mit der angegebenen capacity. Erstelle dann innerhalb eines verschachtelten Gültigkeitsbereichs (unter Verwendung von geschweiften Klammern) die angeforderte Anzahl von ConnectionGuard-Objekten und speichere sie in einem Vektor. Gib für jeden Guard aus, ob die connection erfolgreich hergestellt wurde:

    • Wenn connected: Guard N: Connected
    • Wenn nicht connected: Guard N: Failed to connect

    (wobei N bei 1 beginnt)

    Nachdem der Gültigkeitsbereich endet (die Guards werden zerstört), gib After scope: X connections available aus, um den finalen Zustand des Pools anzuzeigen.

Beispielsweise 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

Beachte, wie die connections automatisch freigegeben werden, wenn die Guards ihren Gültigkeitsbereich verlassen – du rufst in deinem main-Code niemals explizit release auf. Die Destruktoren werden in umgekehrter Reihenfolge der Erstellung ausgeführt (der letzte Guard wird zuerst zerstört), und jede erworbene connection wird garantiert zurückgegeben. Das ist die Stärke von RAII: Die Ressourcenbereinigung geschieht automatisch und zuverlässig, unabhängig davon, wie der Gültigkeitsbereich beendet wird.

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 gegebenen Kapazität
    
    // TODO: Erstelle einen verschachtelten Gültigkeitsbereich mit geschweiften Klammern
    {
        // TODO: Erstelle einen vector zum Speichern von ConnectionGuard-Objekten
        // Hinweis: Du musst Zeiger oder Smart Pointer verwenden, da ConnectionGuard
        // einen gelöschten Kopierkonstruktor hat
        
        // TODO: Schleife zum Erstellen von numConnections Guards
        // Für jeden Guard gib entweder aus:
        // "Guard N: Connected" or "Guard N: Failed to connect"
        // wobei N bei 1 beginnt
        
    }
    // Guards werden hier zerstört, wenn der Gültigkeitsbereich endet
    
    // TODO: Print "After scope: X connections available"
    
    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

Übe selbstständig: Online-C++-Compiler