Certains langages (tels que C ++ et les premières versions de PHP) ne prennent pas en charge la finally
partie d'une try ... catch ... finally
construction. Est-ce finally
jamais nécessaire? Parce que le code qu'il contient fonctionne toujours, pourquoi ne devrais-je / ne devrais-je pas simplement placer ce code après un try ... catch
bloc sans finally
clause? Pourquoi en utiliser un? (Je cherche une raison / une motivation pour utiliser / ne pas utiliser finally
, pas une raison pour supprimer la `` capture '' ou pourquoi il est légal de le faire.)
exception-handling
Agi Hammerthief
la source
la source
Réponses:
En plus de ce que d'autres ont dit, il est également possible qu'une exception soit levée à l'intérieur de la clause catch. Considère ceci:
Dans cet exemple, la
Cleanup()
fonction ne s'exécute jamais, car une exception est levée dans la clause catch et la capture suivante la plus élevée dans la pile des appels la détectera. L'utilisation d'un bloc finally supprime ce risque et rend le code plus propre à démarrer.la source
Comme d'autres l'ont mentionné, il n'y a aucune garantie que le code après une
try
instruction s'exécute à moins que vous n'attrapiez toutes les exceptions possibles. Cela dit, ceci:peut être réécrit 1 comme:
Mais ce dernier vous oblige à intercepter toutes les exceptions non gérées, à dupliquer le code de nettoyage et à ne pas oublier de relancer. Ce
finally
n'est donc pas nécessaire , mais c'est utile .C ++ n'a pas
finally
parce que Bjarne Stroustrup pense que RAII est meilleur , ou au moins suffit dans la plupart des cas:1 Le code spécifique pour intercepter toutes les exceptions et relancer sans perdre les informations de trace de pile varie selon la langue. J'ai utilisé Java, où la trace de la pile est capturée lorsque l'exception est créée. En C #, vous utiliseriez simplement
throw;
.la source
handleError()
dans le deuxième cas, non?catch (Throwable t) {}
, avec le bloc try .. catch autour du bloc initial entier (pour attraper aussi les objets jetableshandleError
)handleErro();
ce qui en fera un argument encore meilleur pour expliquer pourquoi les blocs sont finalement utiles (même si ce n'était pas la question d'origine).finally
, ce qui est beaucoup plus nuancé.try
est à l'intérieur ducatch
pour l' exception spécifique . Deuxièmement, il est possible que vous ne sachiez pas si vous pouvez gérer l'erreur correctement jusqu'à ce que vous ayez examiné l'exception, ou que la cause de l'exception vous empêche également de gérer l'erreur (au moins à ce niveau). C'est assez courant lors des E / S. Le retour est là parce que la seule façon de garantir lescleanUp
exécutions est de tout attraper , mais le code d'origine permettrait aux exceptions provenant ducatch (SpecificException e)
bloc de se propager vers le haut.finally
les blocs sont généralement utilisés pour vider les ressources, ce qui peut aider à la lisibilité lors de l'utilisation de plusieurs instructions de retour:contre
la source
finally
. (J'utiliserais du code comme dans le deuxième bloc car les déclarations de retour multiples sont déconseillées là où je travaille.)Comme vous l'avez apparemment déjà supposé, oui, C ++ fournit les mêmes capacités sans ce mécanisme. En tant que tel, à proprement parler, le mécanisme
try
/finally
n'est pas vraiment nécessaire.Cela dit, s'en passer impose des exigences sur la façon dont le reste du langage est conçu. En C ++, le même ensemble d'actions est incarné dans un destructeur de classe. Cela fonctionne principalement (exclusivement?) Car l'invocation de destructeurs en C ++ est déterministe. Ceci, à son tour, conduit à des règles assez complexes sur la durée de vie des objets, dont certaines sont décidément non intuitives.
La plupart des autres langues proposent à la place une forme de collecte des ordures. Bien qu'il y ait des choses sur la collecte des ordures qui sont controversées (par exemple, son efficacité par rapport à d'autres méthodes de gestion de la mémoire), une chose n'est généralement pas: l'heure exacte à laquelle un objet sera "nettoyé" par le garbage collector n'est pas directement liée à la portée de l'objet. Cela empêche son utilisation lorsque le nettoyage doit être déterministe, soit lorsqu'il est simplement requis pour un fonctionnement correct, soit lorsqu'il s'agit de ressources si précieuses que leur nettoyage ne doit pas être retardé arbitrairement.
try
/finally
fournit un moyen pour ces langages de gérer les situations qui nécessitent ce nettoyage déterministe.Je pense que ceux qui prétendent que la syntaxe C ++ pour cette capacité est "moins conviviale" que celle de Java manquent plutôt le point. Pire encore, il leur manque un point beaucoup plus crucial sur la division des responsabilités qui va bien au-delà de la syntaxe et qui a beaucoup plus à voir avec la façon dont le code est conçu.
En C ++, ce nettoyage déterministe se produit dans le destructeur de l'objet. Cela signifie que l'objet peut être (et devrait normalement être) conçu pour se nettoyer après lui-même. Cela rejoint l'essence de la conception orientée objet - une classe doit être conçue pour fournir une abstraction et appliquer ses propres invariants. En C ++, c'est exactement ce que l'on fait - et l'un des invariants qu'il prévoit est que lorsque l'objet est détruit, les ressources contrôlées par cet objet (toutes, pas seulement la mémoire) seront détruites correctement.
Java (et similaire) est quelque peu différent. Bien qu'ils prennent (en quelque sorte) en charge un
finalize
qui pourrait théoriquement fournir des capacités similaires, le support est si faible qu'il est fondamentalement inutilisable (et en fait, pratiquement jamais utilisé).Par conséquent, plutôt que la classe elle - même puisse effectuer le nettoyage requis, le client de la classe doit prendre des mesures pour ce faire. Si nous faisons une comparaison suffisamment courte, il peut sembler à première vue que cette différence est assez mineure et Java est assez compétitif avec C ++ à cet égard. Nous nous retrouvons avec quelque chose comme ça. En C ++, la classe ressemble à ceci:
... et le code client ressemble à ceci:
En Java, nous échangeons un peu plus de code où l'objet est utilisé pour un peu moins dans la classe. Cela ressemble initialement à un compromis assez uniforme. En réalité, c'est loin d'être le cas, car dans la plupart des codes, nous ne définissons la classe qu'à un seul endroit, mais nous l' utilisons à plusieurs endroits. L'approche C ++ signifie que nous écrivons uniquement ce code pour gérer le nettoyage en un seul endroit. L'approche Java signifie que nous devons écrire ce code pour gérer le nettoyage plusieurs fois, à de nombreux endroits - chaque endroit où nous utilisons un objet de cette classe.
En bref, l'approche Java garantit fondamentalement que de nombreuses abstractions que nous essayons de fournir sont "fuyantes" - toutes les classes qui nécessitent un nettoyage déterministe obligent le client de la classe à connaître les détails de ce qu'il faut nettoyer et comment faire le nettoyage , plutôt que ces détails étant cachés dans la classe elle-même.
Bien que je l'ai appelé "l'approche Java" ci-dessus,
try
/finally
et des mécanismes similaires sous d'autres noms ne sont pas entièrement limités à Java. Pour un exemple frappant, la plupart (tous?) Des langages .NET (par exemple, C #) fournissent le même.Les itérations récentes de Java et de C # fournissent également quelque chose à mi-chemin entre Java «classique» et C ++ à cet égard. En C #, un objet qui souhaite automatiser son nettoyage peut implémenter l'
IDisposable
interface, qui fournit uneDispose
méthode qui est (au moins vaguement) similaire à un destructeur C ++. Bien que cela puisse être utilisé via untry
/finally
like en Java, C # automatise un peu plus la tâche avec uneusing
instruction qui vous permet de définir les ressources qui seront créées lors de la saisie d'une étendue, et détruites lorsque la portée sera fermée. Bien que toujours bien en deçà du niveau d'automatisation et de certitude fourni par C ++, il s'agit toujours d'une amélioration substantielle par rapport à Java. En particulier, le concepteur de classe peut centraliser les détails de la façon dontde disposer de la classe dans son implémentationIDisposable
. Tout ce qui reste pour le programmeur client est le moindre fardeau d'écrire uneusing
déclaration pour s'assurer que l'IDisposable
interface sera utilisée quand elle devrait l'être. Dans Java 7 et plus récent, les noms ont été modifiés pour protéger les coupables, mais l'idée de base est fondamentalement identique.la source
Je ne peux pas croire que personne d'autre n'ait soulevé cela (sans jeu de mots) - vous n'avez pas besoin d' une clause catch !
C'est parfaitement raisonnable:
Aucune clause catch nulle part n'est visible, car cette méthode ne peut rien faire d' utile avec ces exceptions; ils sont laissés pour propager sauvegarder la pile d'appels à un gestionnaire qui peut . Attraper et relancer des exceptions dans chaque méthode est une mauvaise idée, surtout si vous relancez simplement la même exception. Cela va complètement à l'encontre de la façon dont le traitement structuré des exceptions est censé fonctionner (et est assez proche du retour d'un "code d'erreur" de chaque méthode, juste sous la "forme" d'une exception).
Ce que cette méthode doit faire, cependant, pour se nettoyer après elle-même, afin que le "monde extérieur" n'ait jamais besoin de savoir quoi que ce soit dans le gâchis dans lequel il s'est engagé. La clause finally fait exactement cela - quel que soit le comportement des méthodes appelées, la clause finally sera exécutée "à la sortie" de la méthode (et il en va de même pour chaque clause finally entre le point auquel l'exception est levée et l'éventuelle clause catch qui le gère); chacun est exécuté comme la pile d'appels "se déroule".
la source
Que se passerait-il si une exception était levée à laquelle vous ne vous attendiez pas? L'essai se terminerait au milieu et aucune clause catch n'est exécutée.
Le bloc enfin est d'aider à cela et de s'assurer que, quelle que soit l'exception, le nettoyage se produira.
la source
finally
, car vous pouvez empêcher les exceptions "inattendues" aveccatch(Object)
oucatch(...)
fourre-tout.Certains langages proposent à la fois des constructeurs et des destructeurs pour leurs objets (par exemple C ++ je crois). Avec ces langages, vous pouvez faire la plupart (sans doute tous) de ce qui se fait habituellement
finally
dans un destructeur. En tant que telle - dans ces langues - unefinally
clause peut être superflue.Dans un langage sans destructeurs (par exemple Java), il est difficile (voire impossible) de réaliser un nettoyage correct sans la
finally
clause. NB - En Java il y a unefinalise
méthode mais il n'y a aucune garantie qu'elle sera jamais appelée.la source
finalise
mais je préférerais ne pas entrer dans les arguments politiques autour des destructeurs / finalités pour le moment.finalise
mais avec à la fois une saveur extensible et un mécanisme de type oop - très expressif et comparable aufinalise
mécanisme d'autres langages.Essayez enfin et essayez d'attraper sont deux choses différentes qui ne partagent que le mot-clé: "essayer". Personnellement, j'aurais aimé voir ça différemment. La raison pour laquelle vous les voyez ensemble est que les exceptions produisent un "saut".
Et essayez enfin est conçu pour exécuter du code même si le flux de programmation saute. Que ce soit à cause d'une exception ou pour toute autre raison. C'est une bonne façon d'acquérir une ressource et de s'assurer qu'elle est nettoyée après sans avoir à se soucier des sauts.
la source
try catch
mais pastry finally
; le code utilisant ce dernier est converti en code utilisant uniquement le premier, en copiant le contenu dufinally
bloc à tous les endroits du code où il peut être nécessaire de l'exécuter.Étant donné que cette question ne spécifie pas C ++ comme langage, je considérerai un mélange de C ++ et Java, car ils adoptent une approche différente de la destruction d'objets, qui est suggérée comme l'une des alternatives.
Raisons pour lesquelles vous pourriez utiliser un bloc finally plutôt que du code après le bloc try-catch
vous revenez tôt du bloc try: considérez ceci
comparé à:
vous revenez tôt du (des) bloc (s) de capture: comparer
contre:
Vous renvoyez les exceptions. Comparer:
contre:
Ces exemples ne semblent pas trop mauvais, mais souvent vous avez plusieurs de ces cas en interaction et plus d'un type d'exception / ressource en jeu.
finally
peut aider à empêcher votre code de devenir un cauchemar de maintenance enchevêtré.Maintenant, en C ++, ceux-ci peuvent être gérés avec des objets basés sur la portée. Mais l'OMI présente deux inconvénients: 1. la syntaxe est moins conviviale. 2. L'ordre de construction étant l'inverse de l'ordre de destruction peut rendre les choses moins claires.
En Java, vous ne pouvez pas accrocher la méthode finalize pour effectuer votre nettoyage, car vous ne savez pas quand cela se produira - (vous pouvez le faire, mais c'est un chemin rempli de conditions de course amusantes - JVM a beaucoup de latitude pour décider quand elle détruit les choses - souvent ce n'est pas quand vous vous y attendez - soit plus tôt ou plus tard que vous ne le pensez - et cela peut changer lorsque le compilateur de points chauds entre en jeu ... soupir ...)
la source
Tout ce qui est logiquement "nécessaire" dans un langage de programmation sont les instructions:
Tout algorithme peut être implémenté en utilisant uniquement les instructions ci-dessus, toutes les autres constructions de langage sont là pour rendre les programmes plus faciles à écrire et plus compréhensibles pour les autres programmeurs.
Voir oldy worldy computer pour le matériel réel en utilisant un ensemble d'instructions aussi minimal.
la source
En fait, le plus grand écart pour moi est généralement dans les langages qui prennent en charge
finally
mais manquent de destructeurs, car vous pouvez modéliser toute la logique associée au "nettoyage" (que je séparerai en deux catégories) via des destructeurs au niveau central sans traiter manuellement le nettoyage logique dans chaque fonction pertinente. Lorsque je vois du code C # ou Java faire des choses comme déverrouiller manuellement des mutex et fermer des fichiers enfinally
blocs, cela semble obsolète et un peu comme le code C lorsque tout cela est automatisé en C ++ via des destructeurs de manière à libérer les humains de cette responsabilité.Cependant, je trouverais toujours une commodité légère si C ++ était inclus
finally
et c'est parce qu'il y a deux types de nettoyages:Le deuxième au moins ne correspond pas de manière aussi intuitive à l'idée de destruction des ressources, bien que vous puissiez le faire très bien avec les gardes de portée qui annulent automatiquement les modifications lorsqu'elles sont détruites avant d'être validées. Il
finally
fournit sans doute au moins un mécanisme légèrement plus simple (juste un tout petit peu) pour le travail que les protecteurs de lunette.Cependant, un mécanisme encore plus simple serait un
rollback
bloc que je n'ai jamais vu dans aucune langue auparavant. C'est un peu mon rêve de pipe si j'ai jamais conçu un langage qui impliquait la gestion des exceptions. Cela ressemblerait à ceci:Ce serait le moyen le plus simple de modéliser les restaurations d'effets secondaires, tandis que les destructeurs sont à peu près le mécanisme parfait pour le nettoyage des ressources locales. Maintenant, cela n'économise que quelques lignes de code supplémentaires de la solution de protection de portée, mais la raison pour laquelle je souhaite voir un langage avec cela est que la restauration des effets secondaires a tendance à être l'aspect le plus négligé (mais le plus délicat) de la gestion des exceptions. dans les langages qui tournent autour de la mutabilité. Je pense que cette fonctionnalité encouragerait les développeurs à réfléchir à la gestion des exceptions de manière appropriée en termes de restauration des transactions chaque fois que les fonctions provoquent des effets secondaires et ne se terminent pas et, en prime, lorsque les gens voient à quel point il peut être difficile de faire correctement des restaurations, ils pourraient préférer écrire plus de fonctions sans effets secondaires en premier lieu.
Il existe également des cas obscurs où vous souhaitez simplement effectuer diverses opérations, quelle que soit la sortie d'une fonction, quelle que soit la façon dont elle s'est terminée, comme l'enregistrement d'un horodatage, par exemple. Il
finally
existe sans doute la solution la plus simple et la plus parfaite pour le travail, car essayer d'instancier un objet uniquement pour utiliser son destructeur dans le seul but de journaliser un horodatage semble vraiment bizarre (même si vous pouvez le faire très bien et très facilement avec des lambdas ).la source
Comme tant d'autres choses inhabituelles sur le langage C ++, l'absence de
try/finally
construction est un défaut de conception, si vous pouvez même l'appeler ainsi dans un langage qui semble souvent n'avoir eu aucun travail de conception réel .RAII (l'utilisation de l'appel du destructeur déterministe basé sur la portée sur les objets basés sur la pile pour le nettoyage) a deux défauts graves. Le premier est qu'il nécessite l'utilisation d'objets basés sur la pile , qui sont une abomination qui violent le principe de substitution de Liskov. Il existe de nombreuses bonnes raisons pour lesquelles aucun autre langage OO avant ou depuis que C ++ ne les a utilisées - dans epsilon; D ne compte pas car il est fortement basé sur C ++ et n'a de toute façon aucune part de marché - et expliquer les problèmes qu'ils provoquent dépasse le cadre de cette réponse.
Deuxièmement,
finally
peut faire, c'est un surensemble de destruction d'objets. Une grande partie de ce qui est fait avec RAII en C ++ serait décrite dans le langage Delphi, qui n'a pas de récupération de place, avec le modèle suivant:C'est le modèle RAII rendu explicite; si vous deviez créer une routine C ++ qui ne contient que l'équivalent des première et troisième lignes ci-dessus, ce que le compilateur générerait finirait par ressembler à ce que j'ai écrit dans sa structure de base. Et parce que c'est le seul accès à la
try/finally
construction que C ++ fournit, les développeurs C ++ se retrouvent avec une vue plutôt myopetry/finally
: quand tout ce que vous avez est un marteau, tout commence à ressembler à un destructeur, pour ainsi dire.Mais il y a d'autres choses qu'un développeur expérimenté peut faire avec un
finally
construction. Il ne s'agit pas de destruction déterministe, même face à une exception soulevée; il s'agit de l' exécution de code déterministe , même face à une exception levée.Voici une autre chose que vous pouvez souvent voir dans le code Delphi: un objet de jeu de données auquel sont liés des contrôles utilisateur. L'ensemble de données contient des données provenant d'une source externe et les contrôles reflètent l'état des données. Si vous êtes sur le point de charger un tas de données dans votre ensemble de données, vous souhaiterez désactiver temporairement la liaison de données afin qu'il ne fasse pas des choses étranges à votre interface utilisateur, en essayant de la mettre à jour encore et encore avec chaque nouvel enregistrement entré , vous devez donc le coder comme ceci:
De toute évidence, aucun objet n'est détruit ici et aucun besoin. Le code est simple, concis, explicite et efficace.
Comment cela se ferait-il en C ++? Eh bien, vous devez d'abord coder une classe entière . Ce serait probablement appelé
DatasetEnabler
ou quelque chose comme ça. Son existence entière serait celle d'un assistant RAII. Ensuite, vous devrez faire quelque chose comme ceci:Oui, ces accolades apparemment superflues sont nécessaires pour gérer la portée appropriée et garantir que l'ensemble de données est réactivé immédiatement et non à la fin de la méthode. Donc, ce que vous obtenez ne prend pas moins de lignes de code (sauf si vous utilisez des accolades égyptiennes). Il nécessite la création d'un objet superflu, qui a des frais généraux. (Le code C ++ n'est-il pas censé être rapide?) Il n'est pas explicite, mais s'appuie à la place sur la magie du compilateur. Le code qui est exécuté n'est décrit nulle part dans cette méthode, mais réside à la place dans une classe entièrement différente, éventuellement dans un fichier entièrement différent . Bref, ce n’est en aucun cas une meilleure solution que d’écrire le
try/finally
bloc vous-même.Ce type de problème est suffisamment courant dans la conception d'un langage pour qu'il y ait un nom: l'inversion d'abstraction. Cela se produit lorsqu'une construction de haut niveau est construite au-dessus d'une construction de bas niveau, puis que la construction de bas niveau n'est pas directement prise en charge dans le langage, obligeant ceux qui souhaitent l'utiliser à la réimplémenter en termes de construction de haut niveau, souvent avec des pénalités importantes pour la lisibilité et l'efficacité du code.
la source