Je suis pas sur de voir en quoi c'est etonnant de pouvoir renvoyer un int ou un bool. Tout dépend de l'info qu'on veut faire remonter. On pourrait avoir un code d'erreur par exemple, ou juste retourner une info binaire.
Dans les CppCoreGuidelines, c'est d'ailleurs pas un héritage de std::exception qui est recommandé. Juste de ne pas utiliser des buildin types (parce que c'est plus informatif d'utiliser un type explicite, mais c'est la seule justification). Ca me choquerait pas par exemple de retourner un GlErrorCode ou n'importe quel int de code d'erreur d'une lib C par exemple. Ou un enum.
Dans les premières versions de C++, il n'y avait pas d'exception. Le mécanisme a été ajouté en 1990 dans C++2.0. On s'en passait jusque là.
Quitte à introduire un nouveau mécanisme, autant le faire bien, sans limitations artificielles. On peut donc lever des exceptions de n'importe quel type, plutôt que d'ajouter des types spéciaux avec leurs bizarreries. Le type std::exception et ses dérivés n'ont rien de spécial, sinon que c'est ce qui est défini par la bibliothèque standard.
Sinon, lever une exception avec un type de base, ça ne sert pas à grand chose, en pratique on a plutôt besoin d'exceptions prises dans une hiérarchie de types.
- Edité par michelbillaud 19 juillet 2024 à 12:27:33
Les exceptions se basent sur des interruptions logicielles implémentées par les CPU.
Et les concepteurs de CPU, ils s'en cognent bien fort des comités de normalisation du C++.
Et je pense que les concepteurs de compilateur/chaine de génération d'exécutable etc..., ils vont pas attendre 1998 pour donner de quoi gérer ces "traps" logicielles.
Les concepteurs du C++ n'ont fait qu'entériner ce qui ce faisait déjà sans eux à la norme (et avec pas mal de retard).
Les exceptions C++ ne sont qu'un carrossage/utilisation astucieuse de trucs qui existent, même si cela n'avait pas encore de "réalité" C++.
Un CPU, lui, il a des nombres dans des registres et un opcode qui lui dit de changer son IR (Instruction Register) à partir d'un index dans une table d'interruptions, basta.
Le langage informatique n'est qu'un moyen pour communiquer avec une machine, pas une "réalité autosuffisante purement abstraite".
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Les exceptions dans les langages de programmation de haut niveau, ça remonte à LISP, vers 1958 (comme tant de merveilles :-)). On mesure effectivement le délai pour que ça arrive dans C++.
Il y a deux approches
soit la levée d'une exception provoque l'arrêt de ce qui était en cours et la reprise à un point convenu au départ. C'est ce qui a été choisi pour C++
si elle déclenche l'exécution d'un code, qui peut ou pas faire appel à une continuation
En C++ quand une exception est levée, il faut exécuter les destructeurs de tous les machins alloués automatiquement, jusqu'à retomber sur le code qui gère l'exception. Ça ne repose pas sur les interruptions matérielles.
>Ça ne repose pas sur les interruptions matérielles.
interruptions logicielles
The second scheme, and the one implemented in many production-quality C++ compilers and 64-bit Microsoft SEH, is a table-driven approach.
Mon expérience est lié au CPU Intel, et Windows qui utilise massivement les tables d'interruption.
Dans ce cas, le "lancement" d'un int (souvent l'index dans la table d'interruption) est le cas "naturel". Pas une structure spécifique à un langage, qui, en plus, varie avec le compilateur ou la cible de l'exécutable.
Sauf erreur, le C++ autorise le filtrage sur les conditions de capture d'exceptions, ce qui arrive à systématiquement à :
>si elle déclenche l'exécution d'un code, qui peut ou pas faire appel à une continuation
Non ?
Pour moi, le premier point ("soit la levée d'une exception provoque..."), c'est pas des exceptions "C/C++" (oui, il y a des exceptions en C, même si ce n'est/était pas standardisé) mais plus une mécanique "setjump/longjump", pas d'exception. (Implémenter des exceptions juste avec ça, tu parles d'une galère)
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
interruptions logicielles, c'est le terme utilisé pour les instructions machine qui ont pour but de déclencher une interruption.
Typiquement, l'instruction INT du x86, et les autres "traps".
What Does Software Interrupt Mean?
A software interrupt is a type of interrupt that is caused either by a special instruction in the instruction set or by an exceptional condition in the processor itself.
par opposition aux interruptions matérielles causées par un signal venant de l'extérieur (périphérique par ex) ou d'un timer, etc.
Il y a une analogie jusqu'à un certain degré, mais faut pas pousser à les confondre.
> Pour moi, le premier point ("soit la levée d'une exception provoque..."), c'est pas des exceptions "C/C++"
non j'essayais d'expliquer qu'il y avait à l'époque plusieurs types de mécanismes d'exception dans la nature, et que C++ a choisi une voie.
> >si elle déclenche l'exécution d'un code, qui peut ou pas faire appel à une continuation
C'est qu'en combinant le mécanisme d'exception par "résumption" (lors que l'exception est levée, on fait un truc et on reprend où on a été interrompu) et les continuations, on arrive au mécanisme par "terminaison" (ce qui était en cours est abandonné, l'exécution reprend à un point defini avant)
Pour ça, le truc qu'on exécute embarque une continuation qui a été notée lors du "try" (vers le catch, quoi), ce qui court-circuite la "résumption".
faire l'inverse, simuler la résumption quand on n'a pas la "termination", c'est juste pas possible. Mais apparemment, en pratique c'est pas utile non plus, alors c'est pas grave.
- Edité par michelbillaud 19 juillet 2024 à 10:38:09
C'est très bien Rust, mais ça me paraît difficile de commencer à apprendre à programmer en l'utilisant.
Faut comprendre plus de problématiques (qui est le propriétaire de cet objet ? ), on ne peut pas faire n'importe quoi n'importe comment, ouin, ouin, c'est la dictature du compilateur:-)
- Edité par michelbillaud 19 juillet 2024 à 12:25:44
Je crois que le principal problème à résoudre est : pourquoi veut on lancer une exception?
Car, en fait, il y a trois grandes catégories d'échecs et le fait de lancer une exception ne devrait -- a priori -- être considéré comme "une réaction plausible à un échec" que ... pour une seule de ces catégories.
Passons ces catégories en revue:
une erreur due interface entre la chaise et le clavier
Une grosse proportion des échecs est souvent due à l'interface entre la chaise et le clavier.
Cette interface peut tout aussi bien être le développeur de l'application, par exemple, parce qu'il écrit une instruction mettant une multiplication à l'oeuvre là où il aurait du effectuer une addition ou parce qu'il inverse l'ordre d'exécution de deux instructions que l'utilisateur de l'application, qui introduit -4 lorsque l'on s'attend à recevoir une valeur exclusivement positive (pour calculer la racine carrée, par exemple) ou, pire, qui introduit la chaine "hello world" lorsque l'on s'attendait à recevoir une valeur numérique.
Dans les deux cas, l'origine du problème est -- d'abord et avant tout -- un problème de "pure logique". Et les exceptions ne sont pas adaptées (surtout en C++, qui propose d'autres possibilités) à ce cas d'utilisation. Une assertion qui sera en mesure de signaler le problème en période de tests semble bien plus adaptée, car l'idée est de repérer ces erreurs de logique le plus rapidement possible et de "tracer" le chemin d'exécution qui a permis à cette erreur d'arriver jusqu'à la fonction appelée.
L'idée principale étant que, une fois que l'erreur aura été (correctement) corrigée la vérification elle-même devient inutile pour ce chemin d'exécution particulier et que l'on peut donc tout simplement la "désactiver" en production, car la logique aura été corrigée de manière à empêcher que l'erreur ne se reproduise.
Les échecs prévisibles
Une autre catégorie d'échecs est due au fait que tu t'attends à ce que l'utilisateur de ton application n'en demande plus que ce que tu peux effectivement lui donner.
Mettons que ton application serve à gérer une bibliothèque, et qu'un des clients te demande un livre dont le seul exemplaire a déjà été prêté: Comme tu ne sais pas multiplier cet exemplaire du livre, tu seras bien obligé de dire à ton client que "désolé, mais le seul exemplaire est déjà prêté et ne sera au mieux rendu que pour telle date".
C'est un échec car tu ne peux pas satisfaire la demande qui t'est formulée, mais tu dois t'attendre à ce que cela t'arrive très régulièrement, et tu dois donc, en tant que développeur, "prendre toutes les mesures possibles" pour tenir compte de cet échec.
Encore une fois, l'utilisation des exceptions n'est pas particulièrement adaptée à ce genre de cas : un code d'erreur sera sans doute bien plus utile car cela entre dans le cadre de "la gestion logique" du problème.
Les échecs contre lesquels on ne peut rien
Enfin, il y a une troisième catégories d'échecs : tous ces échecs contre lesquels le développeur d'une fonction ne peut absolument rien faire, pour lesquels il ne peut qu'espérer que "cela n'arrivera jamais", tout en sachant très bien que ... cela arrivera forcément à un moment ou à un autre.
Par exemple, cela peut être un fichier pour lequel on ne dispose pas des droits d'accès nécessaire et auquel on souhaite pourtant accéder : si on n'a pas les accès nécessaires, on ne peut -- tout bonnement -- par faire ce que l'on envisageait de faire avec ce fichier.
Ou bien, cela peut être un serveur distant qui ne répond pas : Sans réponse de la part du serveur, je ne peux pas recevoir les données qui m'intéressent (ou je ne peux pas lui en envoyer).
Et, bien sur, ce genre d'échec va littéralement me bloquer dans l'ensemble de mon processus : tant que je n'ai pas "franchi l'étape" qui consiste à accéder au fichier ou de me connecter au serveur distant, je ne peux tout bonnement absolument rien faire.
Seuelement, cela ne dépend absolument pas de moi, au niveau de la fonction que je développe, si bien que j'espère de tout coeur que cela n'arrivera pas, et, pourtant, je sais très bien que ... c'est un risque qui me pend au nez.
Et ca, c'est un cas d'utilisation des exceptions.
Parce que l'on en arrive à un point où je ne peux plus rien faire et où il est même vraisemblable de penser qu'une bonne partie de la logique et du "chemin d'exécution" qui a mené à ce point particulier doive -- tout bonnement -- être annulé car elle n'avait d'autre but que de me mener à ce point que j'espérais voir fonctonner correctement et qui, au lieu de cela, produit un échec parce que "la lune est juste dans l'alignement de saturne"...
La conclusion de tout cela
La conclusion, c'est que, sur base de ces trois "catégories d'échecs", il n'y en a déjà qu'une seule pour laquelle l'utilisation d'une exception s'avère "intéressante".
Le gros problème étant que certains langages ou même certaines bibliothèques vont décider de lancer une exception pour un oui ou pour un non, en semblant considérer les exceptions comme "la panacée, à utiliser à toutes les sauces".
Or, comme beaucoup de fonctionnalités, les exceptions n'ont pas vocation au remplacement des alternatives : elles ont vocation à fournir une "meilleure solution" dans une situation bien particulière. Et c'est ... dans cette situation bien particulière qu'il est intéressant de les utiliser.
Quid de l'utilisation d'entiers?
Bon, maintenant que l'on a une "bonne idée" de QUAND utiliser une exception, on peut s'intéresser à la manière de l'utiliser. Et Luc l'a très bien dit
lmghs a écrit:
C'était déjà déconseillé. Disons que la syntaxe le permet. Ce n'est pas pour autant qu'il faille le faire.
Cependant, il est ** peut être ** intéressant de pousser la réflexion "un peu plus loin"...
En effet, une exception a pour objectif de signaler "un échec critique", le genre d'échec qui fout ... toute une partie de la logique en l'air parce que "les conditions pour que cette logique fonctionne ne sont définitivement pas remplies, et que le développeur n'y peut absolument rien".
De plus, le but d'une exception est de ... fournir le maximum d'informations possible sur cet échec, de manière, soit à "faire remonter" le problème (il faut envoyer "telle information" au gestionnaire du serveur), soit à donner la possibilité "d'attendre que ca passe, et de réessayer plus tard".
A ce titre, le fait d'utiliser un entier pour représenter la cause de l'échec ne semble pas forcément "déconnante", et c'est sans doute ce qui justifie le fait que la syntaxe permettant de lancer une exception (throw XXX ) nous autorise à les utiliser.
Cela pourrait d'ailleurs être "largement suffisant" sur un projet particulièrement simple.
Le problème, c'est que, dés qu'il y a plus d'un cas "d'échec prévisible auquel on ne pourra rien faire en tant que développeur", l'utilisation d'une valeur entière comme élément représentatif de l'échec va -- sans doute -- nous poser quelques problèmes.
En effet, dés que nous serons confrontés ne serait-ce qu'à deux cas d'échecs prévisibles, nous devrons nous assurer que chaque cas produit une valeur d'échec différente, mais aussi que chaque valeur possible soit traitée "de manière adéquate".
Ainsi, si j'ai un cas où j'essaye d'accéder à un fichier local, et que mes droits d'accès m'en empêche, la réaction sera "contactez le gestionnaire système local pour adapter les droits du fichier".
Et si, "juste à coté", j'ai une autre logique qui nécessite l'accès à un serveur distant qui ne répond pas, la solution sera "contactez le gestionnaire du serveur distant".
Tout le problème étant que le "gestionnaire du système local" et le "gestionnaire du serveur distant" sont sans doute ... deux personnes différentes qu'il faudra contacter de manière différentes en leur fournissant des informations différentes.
Et pour réagir de manière adéquate, nous n'aurons pas d'autre choix que de ... tester les différentes valeurs possibles pour notre "marqueur d'échec", ce qui rendra les choses beaucoup plus complexes à réaliser.
A l'inverse, si on lance -- au moment de l'échec -- des objets de nature différente en fonction du type d'échec auquel on est confronté, on se donne la possibilité de ... traiter chaque situation d'échec séparément, par exemple :
Fichier local inexistant : contacter le gestionnaire du système local "tel fichier n'existe pas"
Acces fichier local refusé : contacter le gestionnaire du système local "modifier les droit d'accès"
Serveur distant ne répondant pas : contacter gestionnaire du serveur distant "votre serveur est down"
Données fichier distant incompatibles : contacter serveur distant "Confirmation format du fichier XXX"
et bien d'autres encore.
Et, non seulement, cela nous permettra de traiter chaque situation sans avoir à nous inquiéter des autres cas possibles, mais, en plus, cela nous permettra de traiter chaque situation au moment "le plus opportun" dans tout la logique de l'application.
Car il est "vraisemblable" que les problèmes liés à un fichier local soient traités à un endroit tout à fait différent du code que les problèmes liés à un serveur distant.
Au final, et pour répondre quand même à ta question: Quel est l'intérêt de pouvoir throw un int?
La réponse, outre le fait qu'il est quand même toujours préférable d'éviter, est : sans doute très limité, mais parfois suffisant pour ce que tu veux faire
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Puisqu'on en est à divaguer à côté de la question posée, je tiens à souligner qu'on ne dit pas
Le programme throw un int
mais
Il lève une exception de type entier.
Et la question qui se pose au programmeur, c'est l'intérêt de le faire (*). L'intérêt de pouvoir le faire, c'est un choix des concepteurs du langage, pour qui la question se pose en fait autrement : quel serait l'intérêt d'empêcher de le faire ? Si un programmeur estime que ça lui est utile dans le cas particulier d'un de ses programmes, pourquoi lui interdire ?
En fait, puisqu'on parle de types de base, entiers, flottants, bref des scalaires, on en arrive aux pointeurs. Est-ce si affreux que ça de lever une exception de type chaîne de caractères ?
if (denominateur == 0) throw "Division par zero" ;
En C++, les chaînes (litérales) n'ont pas toujours été des std::string.... (EDIT: les std::string c'est venu avec C++2.0 et le standard C++98, 16 ans après la première version de C++ diffusée) donc en avant pour char*, comme en C.
(*) scoop, si il le throw, c'est pour catch le int plus loin. Ou alors juste crash pour trace la callstack en debug
- Edité par michelbillaud 31 juillet 2024 à 22:35:06
Quel est l'intérêt de pouvoir throw un int ?
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
Discord NaN. Mon site.
Discord NaN. Mon site.
Discord NaN. Mon site.