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.
Défi
FacileConstruisons 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
ConnectionPooldoit 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 affichezConnection acquired (X available)où X est le nombre restant ; retourneztrueen cas de succès,falsesi aucune connexion n'est disponiblerelease()— augmentez le compteur de connexions disponibles et affichezConnection 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
ConnectionGuardincarne le modèle RAII. Elle doit :- Prendre une référence vers un
ConnectionPooldans 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 connectionavant d'appeler release sur le pool.- Prendre une référence vers un
main.cpp: Démontrez le nettoyage automatique du RAII à travers les portées (scopes).Lisez deux entrées :
- Capacité du pool (entier)
- Nombre de connexions à demander (entier)
Créez un
ConnectionPoolavec la capacité donnée. Ensuite, à l'intérieur d'une portée imbriquée (en utilisant des accolades), créez le nombre demandé d'objetsConnectionGuardstocké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 availablemontrant 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 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;
}
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érique