Acquisition de ressources par initialisation
"L'acquisition de ressources par initialisation" est aussi appelée "L'acquisition de ressource, c'est l'initialisation", traduction directe de l'anglais "ressource acquisition is initialization" (RAII).
Il s'agit d'une technique de programmation qui permet une gestion automatique des ressources informatiques (mémoire, fichiers, locks, connexion à une API, ...). Elle permet en effet d'assurer la libération immédiate des ressources lorsque celles-ci ne sont plus utiles, que ce soit dans le cadre d'un fonctionnement normal ou bien de conditions exceptionnelles (erreurs).
Cette technique est apparue à la suite de discussions sur l'évolution du langage C++ (début des années 90 si ma mémoire ne me fait pas défaut). Certains souhaitaient l'intégration dans C++ d'un mécanisme de ramasse-miettes (garbage collector = gestion automatique de la mémoire), mécanisme déjà présent dans des langages comme LISP. Finalement la technique d'acquisition de ressources par initialisation s'est imposée face à cette approche.
L'efficacité (libération de ressource immédiate), la robustesse et l'universalité (pas uniquement utilisable pour la mémoire) de l'acquisition de ressources par initialisation explique que, bien qu'il existe de bonnes solutions de ramasse-miettes pour C++, ces dernières ne soient jamais devenues populaires.
La technique repose sur la création d'objet dans le tas (heap) et sur un mécanisme de destructeurs appelés automatiquement. Si C++ connaît ces notions, des langage moins complets comme Java ne permettent pas de mettre en place cette technique, il faut alors se contenter du ramasse-miette.
L'article sur Wikipedia
Les pages c++ de Bjarne Stroustrup, voir en particulier les FAQs
Illustrons le mécanisme avec un classe SDEST supposée représenter une chaîne de caractères.
class SDEST { private: char *ptr; public: SDEST() : ptr(0) { if( (ptr = (char *)malloc(128)) == 0 ) throw mon_exception("mémoire insuffisante"); } ~SDEST() { if( ptr ) free(ptr); } SDEST operator << (const char *s); // Opérateur de concaténation sensé gérer automatiquement // l'allocation de mémoire supplémentaire si besoin. };
La classe SDEST respecte le paradigme RAII, car les ressources pouvant être allouées par ses fonctions membres sont systématiquement libérées par le destructeur.
Examinons maintenant une utilisation de la classe SDEST
{ SDEST str; // création dans le tas d'une chaîne. // L'allocation mémoire et l'utilisation de // pointeurs est transparente str << "Bonjour les amis"; // le grossissement de l'espace mémoire éventuellement // nécessaire est géré automatique } // str est détruit automatiquement, la mémoire str.ptr est libérée
On notera que :
- Aucun pointeur n'apparaît (ce qui n'aurait pas été le cas si on avait crée l'objet str avec un new
- Les ressources sont automatiquement libérées, y compris si d'autres traitements (non écrits dans l'exemple) provoquent une exception après la création de l'objet str
- il n'est pas besoin d'écrire du code pour libérer les ressources. En corollaire, la gestion d'exception (try ... catch ...) ne sert plus à faire du nettoyage. On a à écrire une gestion d'exception si et seulement si on veut traiter l'erreur, le nettoyage est lui automatique
Tout cela augmente grandement la robustesse et la simplicité du programme.
L'acquisition par construction n'est pas limitée à la mémoire, mais elle s'applique aux ressources en général. En voici un exemple :
{ MUTEX mux("mymux.mux"); // acquisition d'une sémaphore UNIX ... code critique (protégé contre un accès simultané)... } // libération automatique
Comparons maintenant un même programme avec est sans RAII.
La méthode traditionnelle :
{ FICHIER *f = 0; try { f = new FICHIER("monfichier.txt"); ... delete f; f = 0; } catch( ... ) { if( f ) delete f; throw; } }
Le même programme avec l'acquisition par initialisation est aussi robuste mais d'une structure plus simple et moins sujette à erreur :
{ FICHIER f("monfichier.txt"); ... }
Voici quelques règles :
- toute acquisition de ressource doit être encapsulée selon le principe de la RAII.
- on écrira avec soin les destructeurs des classes utilisant la RAII
- Il ne faut pas oublier que, si un constructeur ne se termine pas avec succès, alors le destructeur correspondant ne sera jamais appelé. On devra donc gérer le cas des objets partiellement construits, en écrivant éventuellement des gestion d'exception de nettoyage dans les constructeurs d'un objet allouant plusieurs ressources (une autre solution consiste à créer d'autre classes encapsulant l'allocation de ressources selon le principe une classe = une ressource).
Commentaires
Afficher les commentaires en Vue non groupée | Vue groupée