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 leistungsfähiges Entwurfsmuster, das die Ressourcenverwaltung an die Lebensdauer von Objekten bindet. Du hast RAII bereits bei Smart Pointern gesehen, aber das Muster gilt für jede Ressource: Dateihandles, Netzwerkverbindungen, Mutexe oder Datenbanktransaktionen.

Die Grundidee ist einfach: Ressourcen im Konstruktor erwerben und sie im Destruktor freigeben. Da C++ garantiert, dass Destruktoren ausgeführt werden, wenn Objekte ihren 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 hier aufgerufen – Datei geschlossen
    
    std::cout << "After scope\n";
}

RAII spielt seine Stärken bei der Verwaltung von Sperren in nebenläufigem Code aus. std::lock_guard der Standardbibliothek folgt diesem Muster – es sperrt einen Mutex bei der Konstruktion und gibt ihn bei der Destruktion frei, wodurch Deadlocks aufgrund vergessener Entsperrungen verhindert werden.

Denke bei der Implementierung von RAII-Klassen daran, entweder Kopier-/Verschiebeoperationen zu löschen oder sie ordnungsgemäß zu implementieren (Rule of Five), um die Duplizierung von Ressourcen oder Probleme durch doppelte Freigabe zu verhindern. RAII verwandelt die fehleranfällige manuelle Ressourcenverwaltung in eine sichere, automatische Bereinigung.

challenge icon

Aufgabe

Einfach

Wir erstellen einen Connection Pool Manager mit RAII, um Datenbankverbindungen sicher zu verwalten. In realen Anwendungen sind Datenbankverbindungen Ressourcen, deren acquire und release ordnungsgemäß erfolgen müssen. Du erstellst einen RAII-Wrapper, der garantiert, dass connections immer an den Pool zurückgegeben werden, selbst wenn Ausnahmen auftreten oder die Codepfade kompliziert werden.

Du organisierst deinen Code auf drei Dateien:

  • ConnectionPool.h: Erstelle einen einfachen Connection Pool, der eine begrenzte Anzahl von connections verwaltet.

    Deine ConnectionPool-Klasse sollte verfolgen, wie viele connections verfügbar sind (beginnend mit einer an den constructor übergebenen capacity). Implementiere:

    • acquire(): Wenn eine connection verfügbar 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 verfügbar sind
    • release(): Erhöhe den count der verfügbaren connections und gib Connection released (X available) aus
    • available(): Gibt die aktuelle Anzahl verfügbarer connections zurück
  • ConnectionGuard.h: Erstelle den RAII-Wrapper, der eine einzelne connection sicher verwaltet.

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

    • In ihrem constructor eine Referenz auf einen ConnectionPool übernehmen und versuchen, eine connection zu erwerben
    • Speichern, ob der Erwerb 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)
    • Den copy constructor und die copy assignment löschen, um die Duplizierung von Ressourcen zu verhindern (Berücksichtigung der Rule of Five)

    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: Veranschauliche die automatische Bereinigung durch RAII mithilfe von Gültigkeitsbereichen.

    Lies zwei Eingaben ein:

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

    Erstelle einen ConnectionPool mit der angegebenen capacity. Erzeuge dann innerhalb eines verschachtelten Gültigkeitsbereichs (mit using curly braces) die angeforderte Anzahl von ConnectionGuard-Objekten, die in einem Vektor gespeichert werden. Gib für jeden Guard aus, ob die Verbindung erfolgreich hergestellt wurde:

    • Wenn verbunden: Guard N: Connected
    • Wenn nicht verbunden: 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, wobei der endgültige Zustand des Pools angezeigt wird.

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 release in deinem main-Code niemals explizit auf. Die Destructors 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 erfolgt automatisch und zuverlässig, unabhängig davon, wie der Gültigkeitsbereich verlassen 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 Copy-Konstruktor hat
        
        // TODO: Schleife zum Erstellen von numConnections Guards
        // Für jeden Guard drucke entweder:
        // "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