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.
Défi
FacileConstruisons 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
ConnectionPooldoit suivre le nombre de connexions disponibles (en commençant par une capacité transmise auconstructor). Implémentez :acquire()— si une connexion est disponible, diminuer le compteur et afficherConnection acquired (X available)où X est le compteur restant ; renvoyertrueen cas de réussite,falsesi aucune connexion n’est disponiblerelease()— augmenter le compteur de connexions disponibles et afficherConnection 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
ConnectionGuardincarne le modèle RAII. Elle doit :- Prendre une référence vers un
ConnectionPooldans sonconstructoret 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
Destructors’exécute, si une connexion était détenue, afficherGuard releasing connectionavant d’appelerreleasesur le pool.- Prendre une référence vers un
main.cpp: démontrez le nettoyage automatique de RAII au moyen de portées.Lisez deux entrées :
- Capacité du pool (entier)
- Nombre de connexions à demander (entier)
Créez un
ConnectionPoolavec la capacité donnée. Ensuite, dans une portée imbriquée (en utilisant des accoladescurly), créez le nombre demandé d’objetsConnectionGuardstocké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 availablepour 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 availableAvec 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 availableRemarquez 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;
}
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
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne