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.
Défi
FacileConstruisons 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
ConnectionPooldoit 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 afficheConnection acquired (X available), où X est le nombre restant ; renvoietrueen cas de réussite etfalsesi aucune connexion n’est disponiblerelease(): augmente le nombre de connexions disponibles et afficheConnection 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
ConnectionGuardincarne le modèle RAII. Elle doit :- Recevoir une référence vers un
ConnectionPooldans 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 connectionavant d’appeler release sur le pool.- Recevoir une référence vers un
main.cpp: démontre le nettoyage automatique de RAII à l’aide de portées.Lis deux entrées :
- Capacité du pool (entier)
- Nombre de connexions à demander (entier)
Crée un
ConnectionPoolavec la capacité fournie. Ensuite, dans une portée imbriquée (à l’aide d’accolades), crée le nombre demandé d’objetsConnectionGuardstocké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 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 availableRemarque 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;
}
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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition ou héritageMixins via CRTPIdiom PimplEffacement de typeClasses enum et typage fortGestion des exceptions en POOHiérarchies d’exceptions personnalisées14Design Patterns – Partie 2
Pattern CommandePattern AdaptateurPattern DécorateurPattern Méthode ModèlePattern ÉtatPattern CompositeRAII comme Pattern3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne