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 bien plus qu’un simple idiome C++ : c’est un puissant modèle de conception qui lie la gestion des ressources à la durée de vie des objets. Tu as déjà vu le RAII avec les pointeurs intelligents, mais ce modèle s’applique à toute ressource : des descripteurs de fichiers, des network connections, des mutex ou des transactions de base de données.

L’idée fondamentale est simple : acquire les ressources dans le constructor, puis les libérer dans le destructeur. Puisque 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 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, empêchant ainsi les interblocages dus à des déverrouillages oubliés.

Lors de l’implémentation de classes RAII, n’oubliez pas de either delete ou d’implémenter correctement les opérations de copy/move (Rule of Five) afin d’éviter les problèmes de duplication des ressources ou de double libération. RAII transforme la gestion manuelle des ressources sujette aux erreurs en un nettoyage sûr et automatique.

challenge icon

Défi

Facile

Construisons un Connection Pool Manager 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. Vous allez créer un 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 deviennent complexes.

Vous allez organiser votre code sur trois fichiers :

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

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

    • acquire() — si une connexion est disponible, diminuer le compteur et afficher Connection acquired (X available) où X est le compteur restant ; renvoyer true en cas de réussite, false si aucune connexion n’est disponible
    • release() — augmenter le compteur de connexions disponibles et afficher Connection released (X available)
    • available() — renvoie le nombre current de connexions disponibles
  • ConnectionGuard.h : construisez le wrapper 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 constructor et tenter d’acquérir une connexion
    • Stocker 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 lors du Destructor (uniquement si une connexion a été acquise)
    • Supprimer le constructeur de copie et l’affectation par copie afin d’empêcher la duplication de ressources (considération liée à la Rule of Five)

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

  • main.cpp : démontrez le nettoyage automatique de RAII au moyen de portées.

    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, dans une portée imbriquée (en utilisant des accolades curly), créez le nombre demandé d’objets ConnectionGuard stockés dans un vecteur. Pour chaque guard, affichez si la connexion a réussi :

    • Si la connexion a réussi : Guard N: Connected
    • Si la connexion n’a pas réussi : Guard N: Failed to connect

    (où N commence à 1)

    Une fois la portée terminée (les guards sont détruits), affichez 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

Remarquez que les connexions sont automatiquement libérées lorsque les guards sortent de leur 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 guard est détruit en premier), et chaque connexion acquise est garantie d’être retournée. C’est 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 gardes
        // Pour chaque garde, afficher soit :
        // "Guard N: Connected" or "Guard N: Failed to connect"
        // où N commence à 1
        
    }
    // Les gardes 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