Pourriez-vous, les développeurs C ++, s'il vous plaît nous donner une bonne description de ce qu'est RAII, pourquoi il est important, et si oui ou non il pourrait avoir une pertinence pour d'autres langages?
Je ne connais un peu. Je crois que cela signifie «L'acquisition de ressources est l'initialisation». Cependant, ce nom ne correspond pas à ma compréhension (peut-être incorrecte) de ce qu'est RAII: j'ai l'impression que RAII est un moyen d'initialiser des objets sur la pile de sorte que, lorsque ces variables sortent de la portée, les destructeurs seront automatiquement être appelé provoquant le nettoyage des ressources.
Alors pourquoi n'est-ce pas appelé "utiliser la pile pour déclencher le nettoyage" (UTSTTC :)? Comment en arriver à "RAII"?
Et comment pouvez-vous créer quelque chose sur la pile qui provoquera le nettoyage de quelque chose qui vit sur le tas? De plus, y a-t-il des cas où vous ne pouvez pas utiliser RAII? Vous êtes-vous déjà retrouvé à souhaiter la collecte des ordures? Au moins un garbage collector que vous pourriez utiliser pour certains objets tout en laissant d'autres être gérés?
Merci.
la source
:(
Je lisais tout le fil, à l'époque, et je ne me considérais même pas comme un débutant en C ++!Réponses:
RAII vous dit quoi faire: Acquérir votre ressource chez un constructeur! J'ajouterais: une ressource, un constructeur. UTSTTC n'est qu'une application de cela, RAII est bien plus.
La gestion des ressources est nul. Ici, la ressource est tout ce qui doit être nettoyé après utilisation. Les études de projets sur de nombreuses plates-formes montrent que la majorité des bogues sont liés à la gestion des ressources - et c'est particulièrement mauvais sous Windows (en raison des nombreux types d'objets et d'allocateurs).
En C ++, la gestion des ressources est particulièrement compliquée en raison de la combinaison d'exceptions et de modèles (de style C ++). Pour un aperçu sous le capot, voir GOTW8 ).
C ++ garantit que le destructeur est appelé si et seulement si le constructeur a réussi. En s'appuyant sur cela, RAII peut résoudre de nombreux problèmes désagréables dont le programmeur moyen pourrait même ne pas être au courant. Voici quelques exemples au-delà du "mes variables locales seront détruites à chaque retour".
Commençons par une
FileHandle
classe trop simpliste employant RAII:Si la construction échoue (avec une exception), aucune autre fonction membre - pas même le destructeur - n'est appelée.
RAII évite d'utiliser des objets dans un état non valide. cela facilite déjà la vie avant même que nous n'utilisions l'objet.
Voyons maintenant les objets temporaires:
Il y a trois cas d'erreur à traiter: aucun fichier ne peut être ouvert, un seul fichier peut être ouvert, les deux fichiers peuvent être ouverts mais la copie des fichiers a échoué. Dans une implémentation non-RAII,
Foo
devrait traiter les trois cas de manière explicite.RAII libère les ressources qui ont été acquises, même lorsque plusieurs ressources sont acquises dans une seule instruction.
Maintenant, agrégons quelques objets:
Le constructeur de
Logger
échouera sioriginal
le constructeur de s échoue (car ilfilename1
n'a pas pu être ouvert),duplex
le constructeur de échoue (carfilename2
n'a pas pu être ouvert) ou si l'écriture dans les fichiers àLogger
l' intérieur du corps du constructeur échoue. Dans aucun de ces cas,Logger
le destructeur de ne sera appelé - nous ne pouvons donc pas compter surLogger
le destructeur de pour libérer les fichiers. Mais s'il aoriginal
été construit, son destructeur sera appelé lors du nettoyage duLogger
constructeur.RAII simplifie le nettoyage après une construction partielle.
Points négatifs:
Des points négatifs? Tous les problèmes peuvent être résolus avec RAII et des pointeurs intelligents ;-)
RAII est parfois difficile à manier lorsque vous avez besoin d'une acquisition différée, en poussant des objets agrégés sur le tas.
Imaginez que le Logger ait besoin d'un
SetTargetFile(const char* target)
. Dans ce cas, la poignée, qui doit toujours être membre deLogger
, doit résider sur le tas (par exemple dans un pointeur intelligent, pour déclencher la destruction de la poignée de manière appropriée).Je n'ai jamais vraiment souhaité la collecte des ordures. Quand je fais du C #, je ressens parfois un moment de bonheur dont je n'ai tout simplement pas besoin de m'en soucier, mais bien plus, tous les jouets cool qui peuvent être créés par destruction déterministe me manquent. (l'utilisation
IDisposable
ne le coupe pas.)J'ai eu une structure particulièrement complexe qui aurait pu bénéficier de GC, où des pointeurs intelligents "simples" provoqueraient des références circulaires sur plusieurs classes. Nous nous sommes trompés en équilibrant soigneusement les pointeurs forts et faibles, mais chaque fois que nous voulons changer quelque chose, nous devons étudier un grand tableau des relations. GC aurait peut-être été meilleur, mais certains des composants contenaient des ressources qui devraient être libérées dès que possible.
Une note sur l'exemple FileHandle: il n'était pas destiné à être complet, juste un échantillon - mais s'est avéré incorrect. Merci Johannes Schaub pour l'avoir signalé et FredOverflow pour l'avoir transformé en une solution C ++ 0x correcte. Au fil du temps, je me suis installé avec l'approche documentée ici .
la source
Foo
propriétaireBar
et laBoz
mute, ...Il existe d'excellentes réponses, alors j'ajoute simplement des choses oubliées.
0. RAII concerne les étendues
RAII concerne les deux:
D'autres ont déjà répondu à ce sujet, je ne vais donc pas m'étendre davantage.
1. Lors du codage en Java ou C #, vous utilisez déjà RAII ...
Comme Monsieur Jourdain l'a fait avec la prose, les gens de C # et même de Java utilisent déjà RAII, mais de manière cachée. Par exemple, le code Java suivant (qui s'écrit de la même manière en C # en remplaçant
synchronized
parlock
):... utilise déjà RAII: L'acquisition du mutex se fait dans le mot-clé (
synchronized
oulock
), et la désacquisition se fera à la sortie du scope.C'est tellement naturel dans sa notation qu'il ne nécessite presque aucune explication, même pour les personnes qui n'ont jamais entendu parler de RAII.
L'avantage de C ++ sur Java et C # ici est que tout peut être fait en utilisant RAII. Par exemple, il n'y a pas d'équivalent intégré direct de
synchronized
nilock
en C ++, mais nous pouvons toujours les avoir.En C ++, il s'écrirait:
qui peut être facilement écrit à la manière Java / C # (en utilisant des macros C ++):
2. RAII a d'autres utilisations
Vous savez quand le constructeur sera appelé (à la déclaration de l'objet), et vous savez quand son destructeur correspondant sera appelé (à la sortie de la portée), vous pouvez donc écrire du code presque magique avec une ligne seulement. Bienvenue au pays des merveilles du C ++ (du moins du point de vue d'un développeur C ++).
Par exemple, vous pouvez écrire un objet compteur (je laisse cela comme un exercice) et l'utiliser simplement en déclarant sa variable, comme l'objet de verrouillage ci-dessus a été utilisé:
qui bien sûr, peut être écrit, encore une fois, de manière Java / C # en utilisant une macro:
3. Pourquoi le C ++ manque-
finally
t-il?La
finally
clause est utilisée en C # / Java pour gérer l'élimination des ressources en cas de sortie d'étendue (via unereturn
exception ou une exception levée).Les lecteurs de spécifications astucieux auront remarqué que C ++ n'a pas de clause finally. Et ce n'est pas une erreur, car C ++ n'en a pas besoin, car RAII gère déjà l'élimination des ressources. (Et croyez-moi, écrire un destructeur C ++ est plus facile que d'écrire la bonne clause Java finally, ou même la méthode Dispose correcte d'un C #).
Pourtant, parfois, une
finally
clause serait cool. Pouvons-nous le faire en C ++? Oui nous pouvons! Et encore une fois avec une utilisation alternative de RAII.Conclusion: RAII est plus qu'une philosophie en C ++: c'est du C ++
Lorsque vous atteignez un certain niveau d'expérience en C ++, vous commencez à penser en termes de RAII , en termes d' exécution automatisée des constructeurs et des destructeurs .
Vous commencez à penser en termes de portées , et les caractères
{
et}
deviennent l'un des plus importants de votre code.Et presque tout convient en termes de RAII: sécurité des exceptions, mutex, connexions de base de données, requêtes de base de données, connexion au serveur, horloges, poignées de système d'exploitation, etc., et enfin, la mémoire.
La partie base de données n'est pas négligeable, car, si vous acceptez de payer le prix, vous pouvez même écrire dans un style « programmation transactionnelle », en exécutant des lignes et des lignes de code jusqu'à décider, au final, si vous voulez valider tous les changements , ou, si ce n'est pas possible, avoir toutes les modifications annulées (tant que chaque ligne satisfait au moins la garantie d'exception forte). (voir la deuxième partie de cet article de Herb's Sutter pour la programmation transactionnelle).
Et comme un puzzle, tout va bien.
RAII fait tellement partie de C ++ que C ++ ne pourrait pas être C ++ sans lui.
Cela explique pourquoi les développeurs C ++ expérimentés sont si amoureux de RAII, et pourquoi RAII est la première chose qu'ils recherchent lorsqu'ils essaient un autre langage.
Et cela explique pourquoi le Garbage Collector, bien qu'il s'agisse d'une technologie magnifique en soi, n'est pas si impressionnant du point de vue d'un développeur C ++:
la source
S'il te plait regarde:
Les programmeurs d'autres langages, en plus du C ++, utilisent, connaissent ou comprennent RAII?
RAII et pointeurs intelligents en C ++
Le C ++ prend-il en charge les blocs «enfin»? (Et quel est ce 'RAII' dont je continue d'entendre parler?)
RAII et exceptions
etc..
la source
RAII utilise la sémantique des destructeurs C ++ pour gérer les ressources. Par exemple, considérons un pointeur intelligent. Vous avez un constructeur paramétré du pointeur qui initialise ce pointeur avec l'adresse de l'objet. Vous allouez un pointeur sur la pile:
Lorsque le pointeur intelligent sort de la portée, le destructeur de la classe pointeur supprime l'objet connecté. Le pointeur est alloué par pile et l'objet - alloué par tas.
Il y a certains cas où RAII n'aide pas. Par exemple, si vous utilisez des pointeurs intelligents de comptage de références (comme boost :: shared_ptr) et créez une structure de type graphique avec un cycle, vous risquez de faire face à une fuite de mémoire car les objets d'un cycle s'empêcheront mutuellement d'être libérés. Le ramassage des ordures aiderait contre cela.
la source
Je voudrais le dire un peu plus fortement que les réponses précédentes.
RAII, Resource Acquisition Is Initialization signifie que toutes les ressources acquises doivent être acquises dans le contexte de l'initialisation d'un objet. Cela interdit l'acquisition de ressources «nues». Le raisonnement est que le nettoyage en C ++ fonctionne sur la base de l'objet, pas sur la base de l'appel de fonction. Par conséquent, tout nettoyage doit être effectué par des objets et non par des appels de fonction. Dans ce sens, C ++ est plus orienté objet que Java. Le nettoyage Java est basé sur les appels de fonction dans les
finally
clauses.la source
Je suis d'accord avec le cpitis. Mais j'aimerais ajouter que les ressources peuvent être n'importe quoi, pas seulement de la mémoire. La ressource peut être un fichier, une section critique, un thread ou une connexion à une base de données.
Elle est appelée Acquisition de ressources est une initialisation car la ressource est acquise lorsque l'objet contrôlant la ressource est construit. Si le constructeur a échoué (c'est-à-dire en raison d'une exception), la ressource n'est pas acquise. Ensuite, une fois que l'objet est hors de portée, la ressource est libérée. c ++ garantit que tous les objets de la pile qui ont été construits avec succès seront détruits (cela inclut les constructeurs des classes de base et des membres même si le constructeur de la super classe échoue).
Le raisonnement derrière RAII est de sécuriser l'exception d'acquisition de ressources. Que toutes les ressources acquises sont correctement libérées, peu importe où une exception se produit. Cependant, cela dépend de la qualité de la classe qui acquiert la ressource (cela doit être exceptionnellement sûr et c'est difficile).
la source
Le problème avec le garbage collection est que vous perdez la destruction déterministe qui est cruciale pour RAII. Une fois qu'une variable sort de la portée, c'est au ramasse-miettes quand l'objet sera récupéré. La ressource détenue par l'objet continuera d'être conservée jusqu'à ce que le destructeur soit appelé.
la source
RAII provient de l'allocation des ressources est l'initialisation. Fondamentalement, cela signifie que lorsqu'un constructeur termine l'exécution, l'objet construit est entièrement initialisé et prêt à être utilisé. Cela implique également que le destructeur libérera toutes les ressources (par exemple, la mémoire, les ressources du système d'exploitation) appartenant à l'objet.
Comparé aux langages / technologies garbage collection (par exemple Java, .NET), C ++ permet un contrôle total de la vie d'un objet. Pour un objet alloué par pile, vous saurez quand le destructeur de l'objet sera appelé (lorsque l'exécution sort du cadre), chose qui n'est pas vraiment contrôlée en cas de garbage collection. Même en utilisant des pointeurs intelligents en C ++ (par exemple, boost :: shared_ptr), vous saurez que lorsqu'il n'y a pas de référence à l'objet pointé, le destructeur de cet objet sera appelé.
la source
Lorsqu'une instance de int_buffer entre en existence, elle doit avoir une taille, et elle allouera la mémoire nécessaire. Lorsqu'il sort de la portée, son destructeur est appelé. Ceci est très utile pour des choses comme les objets de synchronisation. Considérer
Non, pas vraiment.
Jamais. Le garbage collection ne résout qu'un très petit sous-ensemble de la gestion dynamique des ressources.
la source
Il y a déjà beaucoup de bonnes réponses ici, mais je voudrais juste ajouter:
Une explication simple de RAII est que, en C ++, un objet alloué sur la pile est détruit chaque fois qu'il est hors de portée. Cela signifie qu'un destructeur d'objets sera appelé et pourra effectuer tous les nettoyages nécessaires.
Cela signifie que si un objet est créé sans "nouveau", aucune "suppression" n'est requise. Et c'est aussi l'idée derrière les "pointeurs intelligents" - ils résident sur la pile et encapsulent essentiellement un objet basé sur le tas.
la source
RAII est un acronyme pour Resource Acquisition Is Initialization.
Cette technique est tout à fait unique au C ++ en raison de leur prise en charge à la fois des constructeurs et des destructeurs et presque automatiquement les constructeurs qui correspondent aux arguments passés ou dans le pire des cas, le constructeur par défaut est appelé & destructeurs si l'explicité fournie est appelée sinon celle par défaut qui est ajouté par le compilateur C ++ est appelé si vous n'avez pas écrit explicitement un destructeur pour une classe C ++. Cela ne se produit que pour les objets C ++ gérés automatiquement, c'est-à-dire qui n'utilisent pas le magasin libre (mémoire allouée / désallouée à l'aide des opérateurs C ++ new, new [] / delete, delete []).
La technique RAII utilise cette fonction d'objet géré automatiquement pour gérer les objets créés sur le tas / magasin libre en demandant explicitement plus de mémoire en utilisant new / new [], qui devrait être explicitement détruite en appelant delete / delete [] . La classe de l'objet géré automatiquement encapsulera cet autre objet créé sur le tas / mémoire libre. Par conséquent, lorsque le constructeur de l'objet géré automatiquement est exécuté, l'objet encapsulé est créé sur le tas / mémoire libre et lorsque le handle de l'objet géré automatiquement sort de la portée, le destructeur de cet objet géré automatiquement est appelé automatiquement dans lequel le l'objet est détruit à l'aide de delete. Avec les concepts POO, si vous enveloppez de tels objets dans une autre classe dans une portée privée, vous n'auriez pas accès aux membres et méthodes des classes encapsulées & c'est la raison pour laquelle les pointeurs intelligents (aka classes de poignée) sont conçus pour. Ces pointeurs intelligents exposent l'objet encapsulé en tant qu'objet typé au monde externe et là-bas en permettant d'invoquer tous les membres / méthodes dont l'objet mémoire exposé est composé. Notez que les pointeurs intelligents ont différentes saveurs en fonction de différents besoins. Vous devriez vous référer à la programmation C ++ moderne d'Andrei Alexandrescu ou à l'implémentation / documentation de la bibliothèque boost (www.boostorg) shared_ptr.hpp pour en savoir plus. J'espère que cela vous aidera à comprendre RAII. Vous devriez vous référer à la programmation C ++ moderne d'Andrei Alexandrescu ou à l'implémentation / documentation de la bibliothèque boost (www.boostorg) shared_ptr.hpp pour en savoir plus. J'espère que cela vous aidera à comprendre RAII. Vous devriez vous référer à la programmation C ++ moderne d'Andrei Alexandrescu ou à l'implémentation / documentation de la bibliothèque boost (www.boostorg) shared_ptr.hpp pour en savoir plus. J'espère que cela vous aidera à comprendre RAII.
la source