web-dev-qa-db-fra.com

Qu'entend-on par acquisition de ressource, c'est l'initialisation (RAII)?

Qu'entend-on par acquisition de ressource, c'est l'initialisation (RAII)?

235
John

C'est un nom vraiment terrible pour un concept incroyablement puissant, et peut-être une des choses numéro 1 qui manque aux développeurs C++ lorsqu'ils passent à d'autres langages. Il y a eu un peu de mouvement pour essayer de renommer ce concept en Gestion des ressources liées à l'étendue, bien qu'il ne semble pas encore avoir compris.

Lorsque nous disons "ressource", nous ne parlons pas seulement de mémoire - il peut s'agir de descripteurs de fichier, de sockets réseau, de descripteurs de base de données, GDI objets ... En bref, les éléments dont nous disposons sont limités Nous devons pouvoir contrôler leur utilisation. L’aspect "lié à la portée" signifie que la durée de vie de l’objet est liée à la portée d’une variable, de sorte que lorsque la variable sort de la portée, le destructeur publie le Une propriété très utile de ceci est qu'elle améliore la 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 un

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 pile est déroulée, les variables locales sont détruites, ce qui garantit que notre ressource est nettoyée et ne coule pas.

328
the_mandrill

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

  • encapsuler une ressource dans une classe (dont le constructeur généralement - mais pas nécessairement ** - acquiert la ressource et que son destructeur la libère toujours)
  • utiliser la ressource via une instance locale de la classe *
  • la ressource est automatiquement libérée lorsque l'objet sort de la portée

Cela garantit que quoi qu'il se passe pendant l'utilisation de la ressource, celle-ci sera éventuellement libérée (que ce soit en raison d'un retour normal, de la destruction de l'objet contenu ou d'une exception levé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, votre code est beaucoup plus propre, vous n'avez pas besoin de mélanger le code de traitement des erreurs avec la fonctionnalité principale.

*pdate: "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 objet propriétaire.

**pdate2: Comme @sbi l'a fait remarquer, 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.

113
Péter Török

"RAII" signifie "L'acquisition d'une ressource est une initialisation" et est en fait assez impropre, car ce n'est pas une acquisition de ressource (et l'initialisation d'un objet ) elle concerne, mais libère la ressource (au moyen de destruction d'un objet).
Mais RAII est le nom que nous avons et ça colle.

En son cœur même, l’idiome contient des ressources encapsulantes (blocs de mémoire, fichiers ouverts, mutex déverrouillés, you-name-it) dans , objets automatiques locaux , et que le destructeur de cet objet libère la ressource lorsque l'objet est détruit à la fin de la portée à laquelle il appartient:

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

Bien sûr, les objets ne sont pas toujours des objets locaux, automatiques. Ils pourraient aussi être membres d'une classe:

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

Si de tels objets gèrent la mémoire, ils sont souvent appelés "pointeurs intelligents".

Il existe de nombreuses variantes de cela. Par exemple, dans les premiers extraits de code, la question qui se pose est de savoir ce qui se passerait si quelqu'un voulait copier obj. La solution la plus simple serait tout simplement d’interdire la copie. std::unique_ptr<>, un pointeur intelligent faisant partie de la bibliothèque standard décrite par le prochain standard C++, effectue cette opération.
Un autre pointeur intelligent, std::shared_ptr présente la "propriété partagée" de la ressource (un objet alloué dynamiquement) qu’elle détient. Autrement dit, il peut être librement copié et toutes les copies se réfèrent au même objet. Le pointeur intelligent garde la trace du nombre de copies faisant référence au même objet et le supprime lorsque le dernier est en cours de destruction.
Une troisième variante est décrite par std::auto_ptr qui implémente une sorte de sémantique de déplacement: un objet n’appartient qu’à un seul pointeur. Toute tentative de copie d’un objet entraîne (par le biais d’une hackery syntaxique) le transfert de la propriété de cet objet à la cible de la copie.

45
sbi

Le livre Programmation C++ avec les modèles de conception révélés décrit RAII comme:

  1. Acquisition de toutes les ressources
  2. Utilisation des ressources
  3. Libérer des ressources

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

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

11
Dennis

La durée de vie d'un objet est déterminée par sa portée. Cependant, nous avons parfois besoin, ou il est utile, de créer un objet qui vit indépendamment de la portée où il a été créé. En C++, l'opérateur new est utilisé pour créer un tel objet. Et pour détruire l'objet, l'opérateur delete peut être utilisé. Les objets créés par l'opérateur new sont alloués dynamiquement, c'est-à-dire alloués en mémoire dynamique (également appelés tas ou magasin gratuit ). Ainsi, un objet créé par new continuera d'exister jusqu'à ce qu'il soit explicitement détruit à l'aide de delete.

Certaines erreurs qui peuvent survenir lors de l'utilisation de new et delete sont les suivantes:

  • Objet ayant fui (ou mémoire): utiliser new pour allouer un objet et oublier de delete l'objet.
  • Suppression prématurée (ou référence en suspens ): maintien d'un autre pointeur sur un objet , delete l'objet, puis utilisez l'autre pointeur.
  • Double supprimer : essayer de delete un objet deux fois.

Généralement, les variables de portée sont préférées. Cependant, RAII peut être utilisé comme alternative à new et delete pour faire vivre un objet indépendamment de sa portée. Une telle technique consiste à placer le pointeur sur l’objet alloué sur le tas et à le placer dans un objet handle/manager . Ce dernier a un destructeur qui se chargera de détruire l'objet. Cela garantira que l'objet est disponible pour toute fonction qui veut y accéder, et qu'il est détruit lorsque la durée de vie de l'objet handle se termine, sans la nécessité d'un nettoyage explicite.

Des exemples de la bibliothèque standard C++ qui utilisent RAII sont std::string et std::vector.

Considérons ce morceau de code:

void fn(const std::string& str)
{
    std::vector<char> vec;
    for (auto c : str)
        vec.Push_back(c);
    // do something
}

lorsque vous créez un vecteur et que vous y insérez des éléments, vous ne vous souciez pas de leur attribuer ou de les désallouer. Le vecteur utilise new pour allouer de l'espace pour ses éléments sur le tas, et delete pour libérer cet espace. En tant qu'utilisateur de Vector, vous ne vous souciez pas des détails de la mise en œuvre et vous ferez confiance à Vector pour ne pas fuir. Dans ce cas, le vecteur est l’objet handle de ses éléments.

Les autres exemples de la bibliothèque standard qui utilisent RAII sont std::shared_ptr, std::unique_ptr, et std::lock_guard.

Un autre nom pour cette technique est SBRM , abréviation de Gestion des ressources liées à la portée .

9
elmiomar

La gestion manuelle de la mémoire est un cauchemar que les programmeurs inventent depuis l’invention du compilateur. Les langages de programmation avec les éboueurs facilitent la vie, mais au détriment des performances. Dans cet article - Éliminer le ramasse-miettes: la méthode RAII , Peter Goodspeed-Niklaus, ingénieur chez Toptal, nous raconte l’histoire des éboueurs et explique comment les notions de propriété et d'emprunt peuvent aider à éliminer les éboueurs sans compromettre les garanties de sécurité.

5
Dmitry Pavlov

Une classe RAII comprend trois parties:

  1. La ressource est abandonnée dans le destructeur
  2. Les instances de la classe sont allouées par pile
  3. La ressource est acquise dans le constructeur. Cette partie est optionnelle mais commune.

RAII signifie "L'acquisition des ressources est une initialisation". La partie "acquisition de ressources" de RAII est l'endroit où vous commencez quelque chose qui doit être terminé plus tard, tels que:

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

La partie "is initialization" signifie que l'acquisition a lieu à l'intérieur du constructeur d'une classe.

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

5
Mohammad Moridi