Qu'est-ce que RAII? Exemples?

19

Toujours lorsque le terme RAII est utilisé, les gens parlent en fait de déconstruction plutôt que d'initialisation. Je pense que j'ai une compréhension de base de ce que cela pourrait signifier, mais je ne suis pas tout à fait sûr. Aussi: le C ++ est-il le seul langage RAII? Et Java ou C # /. NET?

Felix Dombek
la source

Réponses:

25

L'acquisition de ressources est l'initialisation signifie que les objets doivent se prendre en charge en tant que package complet et ne pas s'attendre à ce qu'un autre code indique à une instance "hé au fait, vous allez bientôt être nettoyé - veuillez ranger maintenant". Cela signifie généralement qu'il y a quelque chose de significatif dans le destructeur. Cela signifie également que vous écrivez une classe spécifiquement pour gérer les ressources, sachant que dans certaines circonstances difficiles à prévoir, comme des exceptions levées, vous pouvez compter sur l'exécution de destructeurs.

Supposons que vous vouliez écrire du code où vous allez changer le curseur Windows en un curseur d'attente (sablier, beignet non fonctionnel, etc.), faites votre travail, puis changez-le à nouveau. Et dites aussi que «faites votre truc» pourrait lever une exception. La manière RAII de le faire serait de créer une classe dont le ctor positionnerait le curseur en attente, dont la seule "vraie" méthode ferait ce que vous voudriez faire, et dont le dtor redéfinirait le curseur. Les ressources (dans ce cas, l'état du curseur) sont liées à la portée d'un objet. Lorsque vous acquérez la ressource, vous initialisez un objet. Vous pouvez compter sur la destruction de l'objet si des exceptions sont levées, ce qui signifie que vous pouvez compter sur le nettoyage de la ressource.

Bien utiliser RAII signifie que vous n'avez pas besoin finally. Bien sûr, il repose sur une destruction déterministe, que vous ne pouvez pas avoir en Java. Vous pouvez obtenir une sorte de destruction déterministe en C # et VB.NET avec using.

Kate Gregory
la source
4
Je pense que c'est ce que vous voulez dire, mais vous voudrez peut-être ajouter que la raison pour laquelle Java et C # ne prennent pas en charge RAII est à cause du garbage collector. En C ++, un objet local sera détruit dès qu'il sortira du domaine. En Java / C #, ce n'est pas vrai.
Jason Baker
S'étendant sur le point Jasons, la raison pour laquelle Java et C # ne peuvent pas garantir une destruction rapide est dû à la possibilité de cycles de référence, ce qui signifie qu'il est impossible de déterminer un ordre sûr pour exécuter les destructeurs. Les cycles de référence peuvent également se produire en C ++, mais les implications sont différentes - le programmeur devient responsable de la détermination de l'ordre de destruction et des suppressions explicites. Cette responsabilité est très souvent intégrée à certains destructeurs de classes de niveau supérieur - par exemple, une classe de conteneur est responsable de la destruction de tous les éléments contenus. La «propriété» est la clé.
Steve314
1
@Jason, c'est ce que je voulais dire par «destruction déterministe» - un programmeur C ++ sait quand le destructeur s'exécutera.
Kate Gregory
Je sais que c'est une vieille réponse, mais je suis encore un peu confus. Je viens d'apprendre le terme et certaines informations indiquent que l'acquisition devrait avoir lieu dans le constructeur. Cela n'a pas vraiment de sens pour moi et cette réponse semble le contredire, mais pourriez-vous clarifier?
Per Johansson
1
@PerJohansson Oui, vous acquérez dans le ctor. Et vous relâchez dans le dtor. Je me concentrais sur le deuxième point, mais ils vont de pair. Une fois le ctor terminé, vous savez que vous avez un objet valide. Et vous savez que quoi qu'il arrive, la ressource sera publiée au bon moment.
Kate Gregory
4

RAII consiste en partie à décider quand un objet devient responsable de son propre nettoyage - la règle étant que l'objet devient responsable si et quand l'initialisation de son constructeur se termine. La symétrie de l'initialisation et du nettoyage, constructeur et destructeur, signifie que les deux ont des liens étroits l'un avec l'autre.

Un point de RAII est d'assurer la sécurité des exceptions - que l'application reste cohérente lorsque des exceptions sont levées. À première vue, cela est trivial - lorsqu'une exception entraîne la fermeture d'une étendue, les variables locales de cette étendue doivent être détruites.

Mais que se passe-t-il si le lancement d'exception se produit dans un constructeur?

Eh bien, l'objet n'a pas été entièrement construit, il ne peut donc pas être détruit en toute sécurité. Le constructeur doit avoir essayé des blocs au besoin pour s'assurer que tous les nettoyages nécessaires sont effectués avant que l'exception ne soit propagée. Une fois que l'exception se propage en dehors de la portée où l'objet a été construit, il n'y aura plus d'appel de destructeur, car l'objet n'a pas été construit en premier lieu.

Considérez en particulier les constructeurs des données de membre à l'intérieur de l'objet en cours de destruction. Si l'un de ces éléments lève une exception, votre code de constructeur principal ne s'exécutera pas du tout - mais un code qui forme une partie implicite de ce constructeur aura. Tous les membres qui ont été construits avec succès seront automatiquement détruits. Tous les membres qui n'ont pas été construits (y compris celui qui a levé l'exception) ne le sont pas.

Donc, fondamentalement, RAII est une politique qui garantit que tout ce qui est entièrement construit sera détruit en temps opportun, en particulier en présence de levées d'exceptions, et que tout objet soit entièrement construit ou ne l'est pas (il n'y a pas de demi- des objets construits que vous ne savez pas comment nettoyer en toute sécurité). Les ressources allouées sont également libérées. Et une grande partie du travail est automatisée, donc le programmeur n'a pas à s'en soucier trop.

Steve314
la source