C ++ prend-il en charge les blocs « enfin »?
Qu'est-ce que l' idiome RAII ?
Quelle est la différence entre l'idiome RAII de C ++ et l'instruction 'using' de C # ?
C ++ prend-il en charge les blocs « enfin »?
Qu'est-ce que l' idiome RAII ?
Quelle est la différence entre l'idiome RAII de C ++ et l'instruction 'using' de C # ?
Réponses:
Non, C ++ ne prend pas en charge les blocs «enfin». La raison en est que C ++ prend en charge RAII: "L'acquisition de ressources est l'initialisation" - un mauvais nom † pour un concept vraiment utile.
L'idée est que le destructeur d'un objet est responsable de la libération des ressources. Lorsque l'objet a une durée de stockage automatique, le destructeur de l'objet sera appelé lorsque le bloc dans lequel il a été créé se termine - même lorsque ce bloc est quitté en présence d'une exception. Voici l'explication de Bjarne Stroustrup sur le sujet.
Une utilisation courante de RAII est le verrouillage d'un mutex:
RAII simplifie également l'utilisation d'objets en tant que membres d'autres classes. Lorsque la classe propriétaire 'est détruite, la ressource gérée par la classe RAII est libérée car le destructeur de la classe gérée RAII est appelé en conséquence. Cela signifie que lorsque vous utilisez RAII pour tous les membres d'une classe qui gèrent des ressources, vous pouvez utiliser un destructeur très simple, peut-être même par défaut, pour la classe propriétaire, car il n'a pas besoin de gérer manuellement les durées de vie de ses ressources membres. . (Merci à Mike B de l' avoir signalé.)
Pour ceux qui connaissent C # ou VB.NET, vous pouvez reconnaître que RAII est similaire à la destruction déterministe .NET en utilisant des instructions IDisposable et «using» . En effet, les deux méthodes sont très similaires. La principale différence est que RAII libérera de manière déterministe tout type de ressource, y compris la mémoire. Lors de l'implémentation d'IDisposable dans .NET (même le langage .NET C ++ / CLI), les ressources seront libérées de manière déterministe, à l'exception de la mémoire. Dans .NET, la mémoire n'est pas libérée de façon déterministe; la mémoire n'est libérée que pendant les cycles de récupération de place.
† Certaines personnes croient que «la destruction est l'abandon des ressources» est un nom plus précis pour l'idiome RAII.
la source
En C ++, le finalement n'est PAS requis en raison de RAII.
RAII déplace la responsabilité de la sécurité des exceptions de l'utilisateur de l'objet vers le concepteur (et le réalisateur) de l'objet. Je dirais que c'est le bon endroit car vous n'avez alors besoin d'obtenir une sécurité d'exception correcte qu'une seule fois (dans la conception / mise en œuvre). En utilisant enfin, vous devez obtenir une sécurité d'exception correcte à chaque fois que vous utilisez un objet.
IMO aussi le code semble plus net (voir ci-dessous).
Exemple:
Un objet de base de données. Pour vous assurer que la connexion DB est utilisée, elle doit être ouverte et fermée. En utilisant RAII, cela peut être fait dans le constructeur / destructeur.
C ++ comme RAII
L'utilisation de RAII facilite l'utilisation d'un objet DB correctement. L'objet DB se fermera correctement par l'utilisation d'un destructeur, peu importe comment nous essayons de l'abuser.
Java comme enfin
Lors de l'utilisation finale, l'utilisation correcte de l'objet est déléguée à l'utilisateur de l'objet. c'est-à-dire qu'il incombe à l'utilisateur de l'objet de fermer correctement la connexion DB. Vous pouvez maintenant faire valoir que cela peut être fait dans le finaliseur, mais les ressources peuvent avoir une disponibilité limitée ou d'autres contraintes et donc vous voulez généralement contrôler la libération de l'objet et ne pas compter sur le comportement non déterministe du garbage collector.
C'est aussi un exemple simple.
Lorsque vous avez plusieurs ressources à libérer, le code peut devenir compliqué.
Une analyse plus détaillée peut être trouvée ici: http://accu.org/index.php/journals/236
la source
// Make sure not to throw exception if one is already propagating.
Il est important que les destructeurs C ++ ne lèvent pas d'exceptions également pour cette raison.RAII est généralement meilleur, mais vous pouvez facilement avoir la sémantique enfin en C ++. En utilisant une petite quantité de code.
En outre, les directives de base C ++ donnent enfin.
Voici un lien vers l' implémentation GSL Microsoft et un lien vers l' implémentation Martin Moene
Bjarne Stroustrup a déclaré à plusieurs reprises que tout ce qui se trouve dans le GSL signifiait éventuellement entrer dans la norme. Ce devrait donc être une manière pérenne de l'utiliser enfin .
Vous pouvez facilement vous implémenter si vous le souhaitez, continuez à lire.
En C ++ 11 RAII et lambdas permettent de faire enfin un général:
exemple d'utilisation:
la sortie sera:
Personnellement, j'ai utilisé cela plusieurs fois pour garantir la fermeture du descripteur de fichier POSIX dans un programme C ++.
Avoir une vraie classe qui gère les ressources et évite ainsi tout type de fuite est généralement mieux, mais cela est finalement utile dans les cas où créer une classe sonne comme une surpuissance.
En outre, je l'aime mieux que les autres langages enfin parce que s'il est utilisé naturellement, vous écrivez le code de fermeture à proximité du code d'ouverture (dans mon exemple, le nouveau et supprimer ) et la destruction suit la construction dans l'ordre LIFO comme d'habitude en C ++. Le seul inconvénient est que vous obtenez une variable automatique que vous n'utilisez pas vraiment et la syntaxe lambda la rend un peu bruyante (dans mon exemple à la quatrième ligne, seul le mot enfin et le bloc {} à droite sont significatifs, le le repos est essentiellement du bruit).
Un autre exemple:
Le membre de désactivation est utile si le finalement doit être appelé uniquement en cas d'échec. Par exemple, vous devez copier un objet dans trois conteneurs différents, vous pouvez configurer enfin pour annuler chaque copie et désactiver une fois toutes les copies réussies. Ce faisant, si la destruction ne peut pas être lancée, vous garantissez une garantie solide.
exemple de désactivation :
Si vous ne pouvez pas utiliser C ++ 11, vous pouvez enfin l' avoir , mais le code devient un peu plus long. Définissez simplement une structure avec seulement un constructeur et un destructeur, le constructeur prend des références à tout ce qui est nécessaire et le destructeur fait les actions dont vous avez besoin. C'est essentiellement ce que fait le lambda, fait manuellement.
la source
FinalAction
c'est essentiellement le même que l'ScopeGuard
idiome populaire , mais avec un nom différent.Au-delà de faciliter le nettoyage avec des objets basés sur la pile, RAII est également utile car le même nettoyage «automatique» se produit lorsque l'objet est membre d'une autre classe. Lorsque la classe propriétaire est détruite, la ressource gérée par la classe RAII est nettoyée car le dtor de cette classe est appelé en conséquence.
Cela signifie que lorsque vous atteignez le nirvana RAII et que tous les membres d'une classe utilisent RAII (comme des pointeurs intelligents), vous pouvez vous en tirer avec un dtor très simple (peut-être même par défaut) pour la classe propriétaire car il n'a pas besoin de gérer manuellement son durée de vie des ressources des membres.
la source
En fait, les langages basés sur les récupérateurs ont besoin "enfin" de plus. Un garbage collector ne détruit pas vos objets en temps opportun, il ne peut donc pas être utilisé pour nettoyer correctement les problèmes non liés à la mémoire.
En termes de données allouées dynamiquement, beaucoup diraient que vous devriez utiliser des pointeurs intelligents.
Toutefois...
Malheureusement, c'est sa propre chute. Les vieilles habitudes de programmation C meurent dur. Lorsque vous utilisez une bibliothèque écrite en C ou dans un style très C, RAII n'aura pas été utilisé. À moins de réécrire l'intégralité du front-end de l'API, c'est exactement ce avec quoi vous devez travailler. Ensuite, le manque de «enfin» mord vraiment.
la source
CleanupFailedException
. Existe-t-il un moyen plausible d'obtenir un tel résultat en utilisant RAII?SomeObject.DoSomething()
méthode et voudra savoir si (1) elle a réussi, (2) a échoué sans effets secondaires , (3) a échoué avec des effets secondaires auxquels l'appelant est prêt à faire face , ou (4) a échoué avec des effets secondaires auxquels l'appelant ne peut pas faire face. Seul l'appelant saura à quelles situations il peut et ne peut pas faire face; ce dont l'appelant a besoin est un moyen de savoir quelle est la situation. Il est dommage qu'il n'y ait pas de mécanisme standard pour fournir les informations les plus importantes sur une exception.Une autre émulation de bloc "enfin" utilisant les fonctions lambda C ++ 11
Espérons que le compilateur optimisera le code ci-dessus.
Maintenant, nous pouvons écrire du code comme ceci:
Si vous le souhaitez, vous pouvez envelopper cet idiome dans des macros "essayer - enfin":
Maintenant, le bloc "enfin" est disponible en C ++ 11:
Personnellement, je n'aime pas la version "macro" de l'idiome "finalement" et préférerais utiliser la fonction pure "with_finally" même si une syntaxe est plus volumineuse dans ce cas.
Vous pouvez tester le code ci-dessus ici: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Si vous avez besoin d'un bloc enfin dans votre code, les gardes de portée ou les macros ON_FINALLY / ON_EXCEPTION répondront probablement mieux à vos besoins.
Voici un court exemple d'utilisation ON_FINALLY / ON_EXCEPTION:
la source
Désolé d'avoir déterré un tel ancien thread, mais il y a une erreur majeure dans le raisonnement suivant:
Plus souvent qu'autrement, vous devez traiter des objets alloués dynamiquement, des nombres dynamiques d'objets, etc. Dans le bloc d'essai, certains codes peuvent créer de nombreux objets (combien sont déterminés au moment de l'exécution) et leur stocker des pointeurs dans une liste. Maintenant, ce n'est pas un scénario exotique, mais très courant. Dans ce cas, vous voudriez écrire des trucs comme
Bien sûr, la liste elle-même sera détruite lors de la sortie de la portée, mais cela ne nettoiera pas les objets temporaires que vous avez créés.
Au lieu de cela, vous devez emprunter la voie laide:
Aussi: pourquoi est-ce que même les langages gérés fournissent un bloc final malgré les ressources qui sont de toute façon automatiquement désallouées par le garbage collector?
Astuce: il y a plus que vous pouvez faire avec "finalement" plus qu'une simple désallocation de mémoire.
la source
new
ne renvoie pas NULL, il lève une exception à la placestd::shared_ptr
etstd::unique_ptr
directement dans le stdlib.FWIW, Microsoft Visual C ++ prend en charge try, enfin et il a toujours été utilisé dans les applications MFC comme méthode pour intercepter des exceptions graves qui entraîneraient sinon un plantage. Par exemple;
J'ai utilisé cela dans le passé pour faire des choses comme enregistrer des sauvegardes de fichiers ouverts avant de quitter. Certains paramètres de débogage JIT vont cependant briser ce mécanisme.
la source
Comme indiqué dans les autres réponses, C ++ peut prendre en charge des
finally
fonctionnalités similaires. L'implémentation de cette fonctionnalité qui est probablement la plus proche de faire partie du langage standard est celle qui accompagne les C ++ Core Guidelines , un ensemble de meilleures pratiques pour l'utilisation du C ++ édité par Bjarne Stoustrup et Herb Sutter. Une implémentation definally
fait partie de la bibliothèque de support des directives (GSL). Tout au long des directives, l'utilisation definally
est recommandée lorsque vous traitez avec des interfaces à l'ancienne, et il a également sa propre directive, intitulée Utiliser un objet final_action pour exprimer le nettoyage si aucun descripteur de ressource approprié n'est disponible .Ainsi, non seulement C ++ prend en charge
finally
, il est en fait recommandé de l'utiliser dans de nombreux cas d'utilisation courants.Un exemple d'utilisation de l'implémentation GSL ressemblerait à:
L'implémentation et l'utilisation de GSL sont très similaires à celles de la réponse de Paolo.Bolzoni . Une différence est que l'objet créé par
gsl::finally()
n'a pas l'disable()
appel. Si vous avez besoin de cette fonctionnalité (par exemple, pour retourner la ressource une fois qu'elle est assemblée et qu'aucune exception ne se produira), vous préférerez peut-être l'implémentation de Paolo. Sinon, l'utilisation de GSL est aussi proche que possible des fonctionnalités standardisées.la source
Pas vraiment, mais vous pouvez les émuler dans une certaine mesure, par exemple:
Notez que le bloc finalement peut lui-même lever une exception avant que l'exception d'origine ne soit renvoyée, rejetant ainsi l'exception d'origine. C'est exactement le même comportement que dans un bloc Java finalement. De plus, vous ne pouvez pas utiliser
return
à l'intérieur des blocs try & catch.la source
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
bloc.J'ai créé une
finally
macro qui peut être utilisée presque comme ¹ lefinally
mot - clé en Java; il utilisestd::exception_ptr
et les amis, les fonctions lambda etstd::promise
, donc il nécessiteC++11
ou au-dessus; il utilise également l' extension de l' expression composée GCC, qui est également prise en charge par clang.AVERTISSEMENT : une version antérieure de cette réponse utilisait une implémentation différente du concept avec de nombreuses autres limitations.
Définissons d'abord une classe d'assistance.
Ensuite, il y a la macro réelle.
Il peut être utilisé comme ceci:
L'utilisation de
std::promise
rend très facile à implémenter, mais il introduit probablement aussi un peu de surcharge inutile qui pourrait être évitée en réimplémentant uniquement les fonctionnalités nécessaires à partir destd::promise
.¹ CAVEAT: il y a quelques choses qui ne fonctionnent pas comme la version java de
finally
. Du haut de ma tête:break
déclaration à partir dutry
et descatch()
blocs de, car ils vivent dans une fonction lambda;catch()
bloc aprèstry
: c'est une exigence C ++;try
etcatch()'s
, la compilation échouera car lafinally
macro se développera en code qui voudra retourner avoid
. Cela pourrait être, euh, un vide en ayant unefinally_noreturn
sorte de macro.Dans l'ensemble, je ne sais pas si j'utiliserais ce truc moi-même, mais c'était amusant de jouer avec. :)
la source
catch(xxx) {}
bloc impossible au début de lafinally
macro, où xxx est un type faux uniquement dans le but d'avoir au moins un bloc catch.catch(...)
, non?xxx
dans un espace de noms privé qui ne sera jamais utilisé.J'ai un cas d'utilisation où je pense que
finally
devrait être une partie parfaitement acceptable du langage C ++ 11, car je pense qu'il est plus facile à lire d'un point de vue de flux. Mon cas d'utilisation est une chaîne de threads consommateur / producteur, où une sentinellenullptr
est envoyée à la fin de la course pour fermer tous les threads.Si C ++ le supportait, vous voudriez que votre code ressemble à ceci:
Je pense que c'est plus logique que de mettre votre déclaration finally au début de la boucle, car cela se produit après la sortie de la boucle ... mais c'est un vœu pieux car nous ne pouvons pas le faire en C ++. Notez que la file d'attente
downstream
est connectée à un autre thread, vous ne pouvez donc pas mettre la sentinellepush(nullptr)
dans le destructeur dedownstream
car elle ne peut pas être détruite à ce stade ... elle doit rester en vie jusqu'à ce que l'autre thread reçoive lenullptr
.Voici donc comment utiliser une classe RAII avec lambda pour faire de même:
et voici comment vous l'utilisez:
la source
Comme beaucoup de gens l'ont dit, la solution consiste à utiliser les fonctionnalités C ++ 11 pour éviter enfin les blocages. L'une des fonctionnalités est
unique_ptr
.Voici la réponse de Méphane écrite en utilisant des modèles RAII.
Une autre introduction à l'utilisation de unique_ptr avec les conteneurs de bibliothèque standard C ++ est ici
la source
Je voudrais proposer une alternative.
Si vous voulez enfin que le bloc soit toujours appelé, placez-le juste après le dernier bloc catch (qui devrait probablement être
catch( ... )
pour intercepter une exception inconnue)Si vous voulez enfin bloquer comme dernière chose à faire quand une exception est levée, vous pouvez utiliser une variable locale booléenne - avant de l'exécuter, définissez-la sur false et placez la véritable affectation à la toute fin du bloc try, puis après le blocage du bloc, vérifiez la variable valeur:
la source
Je pense également que RIIA n'est pas un remplacement pleinement utile pour la gestion des exceptions et pour avoir enfin. BTW, je pense aussi que RIIA est un mauvais nom tout autour. J'appelle ces types de concierges de classe et je les utilise beaucoup. 95% du temps, ils n'initialisent ni n'acquièrent de ressources, ils appliquent certains changements sur une base limitée ou prennent quelque chose de déjà configuré et s'assurent qu'ils sont détruits. Ceci étant le nom officiel du modèle obsédé par Internet, je suis abusé pour avoir même suggéré que mon nom pourrait être meilleur.
Je ne pense pas qu'il soit raisonnable d'exiger que chaque configuration compliquée d'une liste de choses ad hoc doive avoir une classe écrite pour la contenir afin d'éviter des complications lors du nettoyage de tout en ayant besoin d'attraper plusieurs types d'exception en cas de problème dans le processus. Cela conduirait à de nombreuses classes ad hoc qui ne seraient tout simplement pas nécessaires autrement.
Oui, c'est bien pour les classes conçues pour gérer une ressource particulière, ou pour les classes génériques conçues pour gérer un ensemble de ressources similaires. Mais, même si toutes les choses impliquées ont de tels wrappers, la coordination du nettoyage peut ne pas être une simple invocation dans l'ordre inverse des destructeurs.
Je pense qu'il est parfaitement logique pour C ++ d'avoir enfin un. Je veux dire, jeez, tellement de morceaux ont été collés dessus au cours des dernières décennies qu'il semble que des gens étranges deviendraient soudainement conservateurs sur quelque chose comme finalement ce qui pourrait être très utile et probablement rien de plus compliqué que d'autres choses qui ont été ajouté (bien que ce soit juste une supposition de ma part.)
la source
la source
finally
ne fait pas.