Menu
Coddy logo textTech

RAII comme Pattern

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 102 sur 104.

RAII (Resource Acquisition Is Initialization) est bien plus qu’une simple idiome de C++ : c’est un puissant patron de conception qui lie la gestion des ressources à la durée de vie des objets. Tu as déjà vu RAII avec les pointeurs intelligents, mais ce patron s’applique à toute ressource : des descripteurs de fichiers, des connexions réseau, des mutex ou des transactions de base de données.

L’idée fondamentale est simple : acquérir les ressources dans le constructeur, les libérer dans le destructeur. Puisque C++ garantit que les destructeurs sont exécutés lorsque les objets sortent de leur portée, le nettoyage s’effectue automatiquement — même lorsque des exceptions se produisent :

#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");
    }  // Destructeur appelé ici - fichier fermé
    
    std::cout << "After scope\n";
}

RAII est particulièrement utile pour gérer les verrous dans du code multithread. Le std::lock_guard de la bibliothèque standard suit ce modèle : il acquiert un mutex lors de sa construction et le libère lors de sa destruction, évitant ainsi les interblocages dus à des déverrouillages oubliés.

Lors de l’implémentation de classes RAII, n’oubliez pas de supprimer ou d’implémenter correctement les opérations de copie et de déplacement (règle des cinq) afin d’éviter les problèmes de duplication ou de double libération des ressources. RAII transforme la gestion manuelle des ressources, sujette aux erreurs, en un nettoyage sûr et automatique.

challenge icon

Défi

Facile

Construisons un gestionnaire de pool de connexions utilisant RAII pour gérer les connexions à la base de données en toute sécurité. Dans les applications réelles, les connexions à la base de données sont des ressources coûteuses qui doivent être correctement acquises et libérées. Tu vas créer un wrapper RAII qui garantit que les connexions sont toujours rendues au pool, même si des exceptions se produisent ou si les chemins d’exécution deviennent complexes.

Tu organiseras ton code sur trois fichiers :

  • ConnectionPool.h : crée un pool de connexions simple qui gère un nombre limité de connexions.

    Ta classe ConnectionPool doit suivre le nombre de connexions disponibles (en commençant par une capacité transmise au constructeur). Implémente :

    • acquire() : si une connexion est disponible, diminue le compteur et affiche Connection acquired (X available), où X est le nombre restant ; renvoie true en cas de réussite et false si aucune connexion n’est disponible
    • release() : augmente le nombre de connexions disponibles et affiche Connection released (X available)
    • available() : renvoie le nombre actuel de connexions disponibles
  • ConnectionGuard.h : construis le wrapper RAII qui gère en toute sécurité une seule connexion.

    Ta classe ConnectionGuard incarne le modèle RAII. Elle doit :

    • Recevoir une référence vers un ConnectionPool dans son constructeur et tenter d’acquérir une connexion
    • Enregistrer si l’acquisition a réussi
    • Fournir une méthode isConnected() pour vérifier si le guard détient une connexion valide
    • Libérer automatiquement la connexion dans le pool via le destructeur (uniquement si une connexion a été acquise)
    • Supprimer le constructeur de copie et l’opérateur d’affectation par copie afin d’empêcher la duplication des ressources (considération liée à la Rule of Five)

    Lorsque le destructeur s’exécute, si une connexion était détenue, affiche Guard releasing connection avant d’appeler release sur le pool.

  • main.cpp : démontre le nettoyage automatique de RAII à l’aide de portées.

    Lis deux entrées :

    1. Capacité du pool (entier)
    2. Nombre de connexions à demander (entier)

    Crée un ConnectionPool avec la capacité fournie. Ensuite, dans une portée imbriquée (à l’aide d’accolades), crée le nombre demandé d’objets ConnectionGuard stockés dans un vecteur. Pour chaque guard, affiche si la connexion a réussi :

    • Si la connexion a réussi : Guard N: Connected
    • Si la connexion a échoué : Guard N: Failed to connect

    (où N commence à 1)

    Une fois la portée terminée (les guards sont détruits), affiche After scope: X connections available pour montrer l’état final du pool.

Par exemple, avec les entrées 2 et 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

Avec les entrées 3 et 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

Remarque comment les connexions sont automatiquement libérées lorsque les guards sortent de leur portée. Tu n’appelles jamais explicitement release dans ton code principal. Les destructeurs s’exécutent dans l’ordre inverse de la construction (le dernier guard est détruit en premier), et chaque connexion acquise est garantie d’être rendue. C’est toute la puissance de RAII : le nettoyage des ressources s’effectue automatiquement et de manière fiable, quelle que soit la façon dont la portée se termine.

Essayez vous-même

#include <iostream>
#include <vector>
#include "ConnectionPool.h"
#include "ConnectionGuard.h"

using namespace std;

int main() {
    // Lire les entrées
    int capacity;
    int numConnections;
    cin >> capacity;
    cin >> numConnections;
    
    // TODO: Créer un ConnectionPool avec la capacité donnée
    
    // TODO: Créer une portée imbriquée en utilisant des accolades
    {
        // TODO: Créer un vector pour stocker des objets ConnectionGuard
        // Indice : Vous devrez utiliser des pointeurs ou des smart pointers car ConnectionGuard
        // a un constructeur de copie supprimé
        
        // TODO: Boucler pour créer numConnections guards
        // Pour chaque guard, afficher soit :
        // "Guard N: Connected" or "Guard N: Failed to connect"
        // où N commence à 1
        
    }
    // Les Guards sont détruits ici lorsque la portée se termine
    
    // TODO: Print "After scope: X connections available"
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet

Entraînez-vous par vous-même : Compilateur C++ en ligne