Menu
Coddy logo textTech

RAII en tant que patron

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 plus qu'un simple idiome C++ - c'est un patron de conception puissant qui lie la gestion des ressources à la durée de vie des objets. Vous avez déjà vu le RAII avec les pointeurs intelligents, mais ce patron s'applique à n'importe quelle ressource : descripteurs de fichiers, connexions réseau, mutex ou transactions de base de données.

L'idée centrale est simple : acquérir les ressources dans le constructeur, les libérer dans le destructeur. Puisque le C++ garantit que les destructeurs s'exécutent lorsque les objets sortent de leur portée, le nettoyage se fait automatiquement - même lorsque des exceptions surviennent :

#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";
}

Le RAII excelle dans la gestion des verrous dans le 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/déplacement (Rule of Five) afin d'éviter les problèmes de duplication de ressources ou de double libération. Le RAII transforme la gestion manuelle des ressources, sujette aux erreurs, en un nettoyage automatique et sûr.

challenge icon

Défi

Facile

Construisons un Gestionnaire de Pool de Connexions en utilisant le RAII pour gérer en toute sécurité les connexions à une base de données. Dans les applications réelles, les connexions aux bases de données sont des ressources coûteuses qui doivent être correctement acquises et libérées. Vous allez créer une enveloppe (wrapper) RAII qui garantit que les connexions sont toujours retournées au pool, même si des exceptions surviennent ou si les chemins d'exécution du code deviennent complexes.

Vous organiserez votre code sur trois fichiers :

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

    Votre classe ConnectionPool doit suivre le nombre de connexions disponibles (commencez par une capacité passée au constructeur). Implémentez :

    • acquire() — si une connexion est disponible, diminuez le compteur et affichez Connection acquired (X available) où X est le nombre restant ; retournez true en cas de succès, false si aucune connexion n'est disponible
    • release() — augmentez le compteur de connexions disponibles et affichez Connection released (X available)
    • available() — retourne le nombre actuel de connexions disponibles
  • ConnectionGuard.h : Construisez l'enveloppe RAII qui gère en toute sécurité une seule connexion.

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

    • Prendre une référence vers un ConnectionPool dans son constructeur et tenter d'acquérir une connexion
    • Stocker si l'acquisition a réussi
    • Fournir une méthode isConnected() pour vérifier si le garde détient une connexion valide
    • Libérer automatiquement la connexion au pool dans le destructeur (uniquement si une connexion a été acquise)
    • Supprimer le constructeur de copie et l'opérateur d'affectation par copie pour empêcher la duplication de ressources (considération de la Règle des Cinq)

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

  • main.cpp : Démontrez le nettoyage automatique du RAII à travers les portées (scopes).

    Lisez deux entrées :

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

    Créez un ConnectionPool avec la capacité donnée. Ensuite, à l'intérieur d'une portée imbriquée (en utilisant des accolades), créez le nombre demandé d'objets ConnectionGuard stockés dans un vecteur. Pour chaque garde, affichez s'il s'est connecté avec succès :

    • Si connecté : Guard N: Connected
    • Si non connecté : Guard N: Failed to connect

    (où N commence à 1)

    Une fois la portée terminée (les gardes sont détruits), affichez After scope: X connections available montrant 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

Remarquez comment les connexions sont automatiquement libérées lorsque les gardes sortent de la portée — vous n'appelez jamais explicitement release dans votre code principal. Les destructeurs s'exécutent dans l'ordre inverse de la construction (le dernier garde construit est détruit en premier), et chaque connexion acquise est garantie d'être retournée. C'est la puissance du RAII : le nettoyage des ressources se produit automatiquement et de manière fiable, peu importe la façon dont on sort de la portée.

Aide-mémoire

RAII (Resource Acquisition Is Initialization) est un patron de conception qui lie la gestion des ressources à la durée de vie des objets. Les ressources sont acquises dans le constructeur et libérées dans le destructeur.

Comme le C++ garantit que les destructeurs s'exécutent lorsque les objets sortent de leur portée, le nettoyage se fait automatiquement — même en cas d'exceptions :

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");
    }  // Le destructeur est appelé ici - le fichier est fermé automatiquement
    
    std::cout << "After scope\n";
}

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

Lors de l'implémentation de classes RAII, supprimez ou implémentez correctement les opérations de copie/déplacement (Règle des cinq) pour éviter les problèmes de duplication de ressources ou de double libération.

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
        // Hint: Vous devrez utiliser des pointeurs ou des pointeurs intelligents car ConnectionGuard
        // a un constructeur de copie supprimé
        
        // TODO: Boucler pour créer numConnections guards
        // Pour chaque guard, afficher soit :
        // "Guard N: Connected" ou "Guard N: Failed to connect"
        // où N commence à 1
        
    }
    // Les guards sont détruits ici quand la portée se termine
    
    // TODO: Afficher "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