RAII en C++ : gérer ses ressources sans fuite (avec un exemple OpenGL)
Le RAII — Resource Acquisition Is Initialization — est sans doute l’idiome le plus important du C++. Mal nommé (il parle en réalité de libération autant que d’acquisition), il est pourtant ce qui rend le C++ moderne sûr et élégant. Ce tutoriel, hérité d’un article historique de Coder Studio, en explique le principe puis l’applique à un cas concret : la gestion des objets OpenGL.
Le problème : les ressources qu’on oublie de libérer
Mémoire allouée, fichier ouvert, connexion réseau, verrou (mutex), texture GPU… Toute ressource acquise doit être libérée. En C, on le fait à la main :
FILE* f = fopen("data.txt", "r");
// ... si une exception ou un return survient ici,
// fclose() n'est jamais appelé → fuite.
fclose(f);
Le moindre chemin de sortie oublié, la moindre exception, et la ressource fuit. Multiplié à l’échelle d’une application, c’est la source de bugs sournois et de plantages.
L’idée du RAII
Le RAII lie la durée de vie d’une ressource à celle d’un objet sur la pile. On acquiert la ressource dans le constructeur, on la libère dans le destructeur. Comme le C++ garantit l’appel du destructeur dès que l’objet sort de sa portée — y compris en cas d’exception —, la libération devient automatique et infaillible.
class Fichier {
FILE* f_;
public:
Fichier(const char* nom) : f_(fopen(nom, "r")) {}
~Fichier() { if (f_) fclose(f_); } // libération garantie
// on interdit la copie pour ne pas fermer deux fois
Fichier(const Fichier&) = delete;
Fichier& operator=(const Fichier&) = delete;
};
Désormais : { Fichier f("data.txt"); /* ... */ } — le fichier se ferme tout seul à l’accolade fermante, quoi qu’il arrive. C’est exactement le mécanisme derrière std::unique_ptr, std::lock_guard ou std::fstream.
Application à OpenGL
OpenGL est une API C : on y crée des ressources GPU (textures, buffers, shaders) via des identifiants entiers qu’il faut penser à détruire avec glDelete*. Un candidat parfait au RAII :
class Texture {
GLuint id_ = 0;
public:
Texture() { glGenTextures(1, &id_); }
~Texture() { if (id_) glDeleteTextures(1, &id_); }
// déplacement autorisé, copie interdite (règle des 5)
Texture(Texture&& autre) noexcept : id_(autre.id_) { autre.id_ = 0; }
GLuint id() const { return id_; }
};
Plus aucun glDeleteTextures oublié : chaque texture se nettoie à la fin de sa portée. C’est précisément l’objet de l’article d’origine, et la raison pour laquelle des références comme g-truc.net (auteur de la bibliothèque GLM) y renvoyaient.
La règle des 5
Dès qu’une classe gère une ressource, le compilateur ne peut plus générer des copies correctes automatiquement. Vous devez décider explicitement du sort de : destructeur, constructeur de copie, affectation de copie, constructeur de déplacement, affectation de déplacement. C’est la règle des 5. Le plus souvent : interdire la copie, autoriser le déplacement — comme ci-dessus.
Le RAII est la fondation des pointeurs intelligents utilisés dans nos tutoriels Factory et Observer. Pour comprendre d’où vient la gestion manuelle qu’il remplace, relisez les bases du langage C et sa section sur les pointeurs.