Qu’entend-on par acquisition de ressources, l’initialisation (RAII)?

Qu’entend-on par acquisition de ressources, l’initialisation (RAII)?

C’est un nom vraiment terrible pour un concept incroyablement puissant, et peut-être l’un des numéros 1 que les développeurs C ++ manquent lorsqu’ils passent à d’autres langages. Il y a eu un certain mouvement pour essayer de renommer ce concept en tant que Scope-Bound Resource Management , même si cela ne semble pas encore avoir été le cas.

Lorsque nous disons “Ressource”, nous ne parlons pas seulement de mémoire – il peut s’agir de descripteurs de fichiers, de sockets réseau, de descripteurs de firebase database, d’objects GDI … Bref, des éléments dont nous disposons et contrôler leur utilisation. L’aspect lié à l’étendue signifie que la durée de vie de l’object est liée à la scope d’une variable. Ainsi, lorsque la variable sort de la scope, le destructeur libère la ressource. Une propriété très utile de ceci est que cela permet une plus grande sécurité des exceptions. Par exemple, comparez ceci:

RawResourceHandle* handle=createNewResource(); handle->performInvalidOperation(); // Oops, throws exception ... deleteResource(handle); // oh dear, never gets called so the resource leaks 

Avec le RAII

 class ManagedResourceHandle { public: ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {}; ~ManagedResourceHandle() {delete rawHandle; } ... // omitted operator*, etc private: RawResourceHandle* rawHandle; }; ManagedResourceHandle handle(createNewResource()); handle->performInvalidOperation(); 

Dans ce dernier cas, lorsque l’exception est levée et que la stack est déroulée, les variables locales sont détruites, ce qui garantit que notre ressource est nettoyée et ne fuit pas.

Ceci est un idiome de programmation qui signifie brièvement que vous

  • encapsuler une ressource dans une classe (dont le constructeur acquiert généralement la ressource, mais pas nécessairement **, et son destructeur la libère toujours)
  • utilise la ressource via une instance locale de la classe *
  • la ressource est automatiquement libérée lorsque l’object est hors de scope

Cela garantit que, quoi qu’il arrive, pendant que la ressource est en cours d’utilisation, elle sera éventuellement libérée (que ce soit en raison d’un retour normal, d’une destruction de l’object contenant ou d’une exception déclenchée).

C’est une bonne pratique largement utilisée en C ++, car en plus d’être un moyen sûr de gérer les ressources, cela rend votre code beaucoup plus propre car vous n’avez pas besoin de combiner le code de gestion des erreurs avec la fonctionnalité principale.

* Mise à jour: “local” peut signifier une variable locale, ou une variable membre non statique d’une classe. Dans ce dernier cas, la variable membre est initialisée et détruite avec son object propriétaire.

** Update2: comme @sbi l’a souligné, la ressource – bien que souvent allouée à l’intérieur du constructeur – peut également être allouée à l’extérieur et transmise en tant que paramètre.

“RAII” signifie “Acquisition de ressources est une initialisation” et est en fait tout à fait impropre, car il ne s’agit pas d’une acquisition de ressources (et de l’initialisation d’un object), mais de la libérer (par destruction d’un object) ).
Mais RAII est le nom qu’on a et ça colle.

En son cœur, l’idiome comporte des ressources d’encapsulation (morceaux de mémoire, fichiers ouverts, mutexes déverrouillés, nom-it) dans des objects locaux et automatiques , et le destructeur de cet object libère la ressource lorsque l’object est détruit au fin du périmètre auquel il appartient:

 { raii obj(acquire_resource()); // ... } // obj's dtor will call release_resource() 

Bien entendu, les objects ne sont pas toujours des objects locaux, automatiques. Ils pourraient être membres d’une classe aussi:

 class something { private: raii obj_; // will live and die with instances of the class // ... }; 

Si de tels objects gèrent de la mémoire, ils sont souvent appelés “pointeurs intelligents”.

Il en existe de nombreuses variantes. Par exemple, dans les premiers extraits de code, la question se pose de savoir ce qui arriverait si quelqu’un voulait copier obj . La solution la plus simple serait de simplement refuser la copie. std::unique_ptr<> , un pointeur intelligent qui fait partie de la bibliothèque standard décrite par le prochain standard C ++, fait cela.
Un autre pointeur intelligent, std::shared_ptr présente la “propriété partagée” de la ressource (un object alloué dynamicment) qu’il contient. C’est-à-dire qu’il peut être librement copié et que toutes les copies font référence au même object. Le pointeur intelligent garde la trace du nombre de copies faisant référence au même object et le supprimera lorsque le dernier sera détruit.
std::auto_ptr présente une troisième variante qui implémente une sorte de sémantique de mouvement: un object appartient à un seul pointeur et toute tentative de copier un object entraîne (via le piratage de syntaxe) le transfert de la propriété de l’object à la cible. de l’opération de copie.

Le livre C ++ Programming with Design Patterns Revealed décrit RAII comme:

  1. Acquérir toutes les ressources
  2. Utiliser des ressources
  3. Libérer des ressources

  • Les ressources sont implémentées en tant que classes, et tous les pointeurs sont entourés de classe (ce qui en fait des pointeurs intelligents).

  • Les ressources sont acquises en invoquant leurs constructeurs et libérées implicitement (dans l’ordre inverse de l’acquisition) en appelant leurs destructeurs.

La gestion manuelle de la mémoire est un cauchemar que les programmeurs ont inventé des manières d’éviter depuis l’invention du compilateur. Les langages de programmation avec les éboueurs facilitent la vie, mais au désortingment des performances. Peter Goodspeed-Niklaus, ingénieur chez Toptal, nous donne un aperçu de l’histoire des éboueurs et explique comment les notions de propriété et d’emprunt peuvent aider à éliminer les éboueurs sans compromettre leurs garanties de sécurité.

Il y a trois parties dans une classe RAII:

  1. La ressource est abandonnée dans le destructeur
  2. Les instances de la classe sont allouées à la stack
  3. La ressource est acquise dans le constructeur. Cette partie est facultative, mais commune.

RAII signifie “L’acquisition de ressources est l’initialisation”. La partie “acquisition de ressources” de RAII est l’endroit où vous commencez quelque chose qui doit être terminé plus tard, tel que:

  1. Ouvrir un fichier
  2. Allouer de la mémoire
  3. Acquisition d’une serrure

La partie “est initialisation” signifie que l’acquisition se produit à l’intérieur du constructeur d’une classe.

https://www.tomdalling.com/blog/software-design/resource-acquisition-is-initialisation-rai-explained/