Qu'entend-on par acquisition de ressources est initialisation (RAII)?
Qu'entend-on par acquisition de ressources est initialisation (RAII)?
Réponses:
C'est un nom vraiment terrible pour un concept incroyablement puissant, et peut-être l'une des choses numéro 1 que les développeurs C ++ manquent lorsqu'ils passent à d'autres langages. Il y a eu un peu de mouvement pour essayer de renommer ce concept en tant que gestion des ressources liée à la portée , bien qu'il ne semble pas encore avoir pris le dessus.
Quand 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 base de données, d'objets GDI ... 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, donc lorsque la variable sort de la portée, le destructeur libère la ressource. Une propriété très utile de ceci est qu'elle permet une plus grande sécurité d'exception. 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 pile est déroulée, les variables locales sont détruites, ce qui garantit que notre ressource est nettoyée et ne fuit pas.
Scope-Bound
c'est le meilleur choix de nom ici, car les spécificateurs de classe de stockage ainsi que la portée déterminent la durée de stockage d'une entité. Le fait de le restreindre à la portée est peut-être une simplification utile, mais il n'est pas précis à 100%
Ceci est un idiome de programmation qui signifie brièvement que vous
Cela garantit que quoi qu'il arrive pendant que la ressource est en cours d'utilisation, elle sera finalement libérée (que ce soit en raison d'un retour normal, de la destruction de l'objet contenant 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, cela rend également votre code beaucoup plus propre car vous n'avez pas besoin de mélanger 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 objet propriétaire.
**
Update2: comme l'a souligné @sbi, la ressource - bien que souvent allouée à l'intérieur du constructeur - peut également être allouée à l'extérieur et passée en paramètre.
open()
/ close()
pour initialiser et libérer la ressource, juste le constructeur et le destructeur, donc la `` conservation '' de la ressource n'est que la durée de vie de l'objet, peu importe si cette durée de vie est géré par le contexte (pile) ou explicitement (allocation dynamique)
"RAII" signifie "L'acquisition de ressources est l'initialisation" et est en fait tout à fait impropre, car il ne s'agit pas de l' acquisition de ressources (et de l'initialisation d'un objet), mais de libérer la ressource (au moyen de la destruction d'un objet). ).
Mais RAII est le nom que nous avons et ça colle.
En son cœur, l'idiome propose des ressources d'encapsulation (morceaux de mémoire, fichiers ouverts, mutex déverrouillés, nommez-le) dans des objets locaux et automatiques , et le destructeur de cet objet libérant la ressource lorsque l'objet est détruit au fin du périmètre auquel 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 et 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 en existe de nombreuses variantes. Par exemple, dans les premiers extraits de code, la question se pose de savoir ce qui se passerait si quelqu'un voulait copier obj
. La solution la plus simple serait de simplement interdire la copie. std::unique_ptr<>
, un pointeur intelligent pour faire partie de la bibliothèque standard telle que présentée par la prochaine norme C ++, le fait.
Un autre pointeur intelligent de ce type 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 copié librement et toutes les copies se réfèrent au même objet. Le pointeur intelligent garde une trace du nombre de copies faisant référence au même objet et le supprimera lorsque la dernière sera détruite.
Une troisième variante est présentée parstd::auto_ptr
qui implémente une sorte de sémantique de déplacement: un objet n'appartient qu'à un seul pointeur, et tenter de copier un objet entraînera (par le biais d'un piratage syntaxique) le transfert de la propriété de l'objet à la cible de l'opération de copie.
std::auto_ptr
est une version obsolète de std::unique_ptr
. std::auto_ptr
sorte de sémantique de mouvement simulé autant que possible en C ++ 98, std::unique_ptr
utilise la nouvelle sémantique de mouvement de C ++ 11. Une nouvelle classe a été créée parce que la sémantique de déplacement de C ++ 11 est plus explicite (nécessite std::move
sauf à partir de temporaire) alors qu'elle était par défaut pour toute copie de non-const dans std::auto_ptr
.
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ée tas ou magasin libre ). 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 se produire lors de l' utilisation new
et delete
sont les suivants :
new
pour allouer un objet et oublier à delete
l'objet.delete
l'objet, puis utiliser l'autre pointeur.delete
deux fois un objet.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 à prendre le pointeur sur l'objet qui a été alloué sur le tas et à le placer dans un objet handle / manager . Ce dernier dispose d'un destructeur qui se chargera de détruire l'objet. Cela garantira que l'objet est disponible pour toute fonction qui souhaite y accéder et que l'objet est détruit lorsque la durée de vie de l' objet handle se termine, sans avoir besoin d'un nettoyage explicite.
Les exemples de la bibliothèque standard C ++ qui utilisent RAII sont std::string
et std::vector
.
Considérez 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 d'allouer et de désallouer ces éléments. Le vecteur utilise new
pour allouer de l'espace à 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 mise en œuvre et vous ferez confiance à vector pour ne pas fuir. Dans ce cas, le vecteur est l' objet poignée de ses éléments.
D' autres exemples de la bibliothèque standard RAII d'utilisation sont std::shared_ptr
, std::unique_ptr
et std::lock_guard
.
Un autre nom pour cette technique est SBRM , abréviation de Scope-Bound Resource Management .
Le livre C ++ Programming with Design Patterns Revealed décrit RAII comme:
Où
Les ressources sont implémentées en tant que classes, et tous les pointeurs ont des wrappers de classe autour d'eux (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 invoquant leurs destructeurs.
Une classe RAII comprend trois parties:
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, comme:
La partie "est l'initialisation" signifie que l'acquisition se produit à l'intérieur du constructeur d'une classe.
La gestion manuelle de la mémoire est un cauchemar que les programmeurs ont inventé des moyens d'éviter depuis l'invention du compilateur. Les langages de programmation avec garbage collector vous facilitent la vie, mais au détriment des performances. Dans cet article - Éliminer le garbage collector: The RAII Way , Peter Goodspeed-Niklaus, ingénieur de Toptal, nous donne un aperçu de l'histoire des ramasseurs de déchets et explique comment les notions de propriété et d'emprunt peuvent aider à éliminer les ramasseurs de déchets sans compromettre leurs garanties de sécurité.