Dois-je utiliser un spécificateur d'exception en C ++?

123

En C ++, vous pouvez spécifier qu'une fonction peut ou non lever une exception à l'aide d'un spécificateur d'exception. Par exemple:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

Je doute de les utiliser réellement pour les raisons suivantes:

  1. Le compilateur n'applique pas vraiment les spécificateurs d'exception de manière rigoureuse, donc les avantages ne sont pas grands. Idéalement, vous aimeriez obtenir une erreur de compilation.
  2. Si une fonction viole un spécificateur d'exception, je pense que le comportement standard est de terminer le programme.
  3. Dans VS.Net, il traite le jet (X) comme un jet (...), donc l'adhésion à la norme n'est pas forte.

Pensez-vous que les spécificateurs d'exception devraient être utilisés?
Veuillez répondre par «oui» ou «non» et fournir quelques raisons pour justifier votre réponse.

1800 INFORMATIONS
la source
7
"throw (...)" n'est pas un C ++ standard. Je crois que c'est une extension dans certains compilateurs et a généralement la même signification qu'aucune spécification d'exception.
Richard Corden

Réponses:

97

Non.

Voici plusieurs exemples de raisons:

  1. Le code du modèle est impossible à écrire avec des spécifications d'exception,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }
    

    Les copies peuvent lancer, le passage de paramètre peut déclencher et x()peut lever une exception inconnue.

  2. Les spécifications d'exception ont tendance à interdire l'extensibilité.

    virtual void open() throw( FileNotFound );

    pourrait évoluer vers

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    Vous pourriez vraiment écrire cela comme

    throw( ... )

    Le premier n'est pas extensible, le second est trop ambitieux et le troisième est vraiment ce que vous entendez quand vous écrivez des fonctions virtuelles.

  3. Code hérité

    Lorsque vous écrivez du code qui repose sur une autre bibliothèque, vous ne savez pas vraiment ce qu'il pourrait faire quand quelque chose va terriblement mal.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }
    

    gse terminera, lorsque lib_f()jette. Ce n'est (dans la plupart des cas) pas ce que vous voulez vraiment. std::terminate()ne devrait jamais être appelé. Il est toujours préférable de laisser l'application planter avec une exception non gérée, à partir de laquelle vous pouvez récupérer une trace de pile, que de mourir silencieusement / violemment.

  4. Écrivez du code qui renvoie les erreurs courantes et les lance dans des occasions exceptionnelles.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }
    

Néanmoins, lorsque votre bibliothèque lève simplement vos propres exceptions, vous pouvez utiliser des spécifications d'exception pour indiquer votre intention.

Christophe
la source
1
en 3, ce serait techniquement std ::xpected et non std :: terminate. Mais lorsque cette fonction est appelée, la valeur par défaut invoque abort (). Cela génère un vidage de mémoire. En quoi est-ce pire qu'une exception non gérée? (qui fait essentiellement la même chose)
Greg Rogers
6
@Greg Rogers: Une exception non corrigée se déroule toujours. Cela signifie que les destructeurs seront appelés. Et dans ces destructeurs, beaucoup peut être fait, comme: des ressources correctement libérées, des journaux correctement écrits, d'autres processus seront informés que le processus actuel plante, etc. Pour résumer, c'est RAII.
paercebal
Vous avez omis: cela enveloppe efficacement tout dans une try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]construction, que vous vouliez ou non un bloc try.
David Thornley
4
@paercebal C'est incorrect. C'est l'implémentation définie si les destructeurs sont exécutés ou non pour les exceptions non interceptées. La plupart des environnements ne déroulent pas les destructeurs de pile / exécution si l'exception n'est pas interceptée. Si vous voulez vous assurer que vos destructeurs fonctionnent de manière portative même lorsqu'une exception est levée et non gérée (cela a une valeur douteuse), vous devez écrire votre code commetry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo
" Il est toujours préférable de laisser l'application planter avec une exception non gérée, à partir de laquelle vous pouvez récupérer une trace de pile, que de mourir silencieusement / violemment. " Comment un "crash" peut-il être meilleur qu'un appel propre terminate()? Pourquoi ne pas simplement appeler abort()?
curiousguy
42

Évitez les spécifications d'exception en C ++. Les raisons que vous donnez dans votre question sont un bon début pour savoir pourquoi.

Voir "Un regard pragmatique sur les spécifications d'exception" de Herb Sutter .

Michael Burr
la source
3
@awoodland: l'utilisation de 'dynamic-exception-specifications' ( throw(optional-type-id-list)) est obsolète en C ++ 11. Ils sont toujours dans la norme, mais je suppose qu'un coup de semonce a été envoyé pour que leur utilisation soit examinée attentivement. C ++ 11 ajoute la noexceptspécification et l'opérateur. Je ne connais pas assez de détails pour noexcepten parler . Cet article semble assez détaillé: akrzemi1.wordpress.com/2011/06/10/using-noexcept Et Dietmar Kühl a un article dans le Overload Journal de juin 2011: accu.org/var/uploads/journals/overload103.pdf
Michael Burr
@MichaelBurr Only throw(something)est considéré comme inutile et une mauvaise idée. throw()est utile.
curiousguy
" Voici ce que beaucoup de gens pensent que les spécifications d'exception font: bullet Garantie que les fonctions ne lèveront que des exceptions répertoriées (éventuellement aucune). Bullet Activer les optimisations du compilateur en sachant que seules les exceptions répertoriées (éventuellement aucune) seront levées. Les attentes ci-dessus sont, encore une fois, faussement proche d'être correct "Non, les attentes ci-dessus sont absolument correctes.
curiousguy
14

Je pense que les
spécificateurs d'exception standard sauf convention (pour C ++) étaient une expérience de la norme C ++ qui a échoué pour la plupart.
L'exception étant que le spécificateur no throw est utile, mais vous devez également ajouter le bloc try catch approprié en interne pour vous assurer que le code correspond au spécificateur. Herb Sutter a une page sur le sujet. Gotch 82

De plus, je pense qu'il vaut la peine de décrire les garanties d'exception.

Il s'agit essentiellement de la documentation sur la manière dont l'état d'un objet est affecté par les exceptions échappant à une méthode sur cet objet. Malheureusement, ils ne sont pas appliqués ou mentionnés par le compilateur.
Boost et exceptions

Garanties d'exception

Aucune garantie:

Il n'y a aucune garantie sur l'état de l'objet après qu'une exception a échappé à une méthode
Dans ces situations, l'objet ne doit plus être utilisé.

Garantie de base:

Dans presque toutes les situations, cela devrait être la garantie minimale offerte par une méthode.
Cela garantit que l'état de l'objet est bien défini et peut toujours être utilisé de manière cohérente.

Garantie forte: (aka garantie transactionnelle)

Cela garantit que la méthode se terminera avec succès
ou qu'une exception sera levée et l'état des objets ne changera pas.

Aucune garantie de jet:

La méthode garantit qu'aucune exception n'est autorisée à se propager hors de la méthode.
Tous les destructeurs devraient faire cette garantie.
| NB Si une exception échappe à un destructeur alors qu'une exception se propage déjà
| l'application se terminera

Martin York
la source
Les garanties sont quelque chose que tout programmeur C ++ doit savoir, mais elles ne me semblent pas liées à des spécifications d'exception.
David Thornley
1
@David Thornley: Je vois les garanties comme ce que les spécifications d'exception auraient dû être (c'est-à-dire qu'une méthode avec le G fort ne peut pas appeler une méthode avec un G de base sans protection). Malheureusement, je ne suis pas sûr qu'ils soient suffisamment bien définis pour être appliqués de manière utile par le compilateur.
Martin York
" Voici ce que de nombreuses personnes pensent que les spécifications d'exception font: - Garantir que les fonctions ne lèveront que des exceptions répertoriées (éventuellement aucune). - Activer les optimisations du compilateur en sachant que seules les exceptions répertoriées (éventuellement aucune) seront levées. Les attentes ci-dessus sont, encore une fois, faussement près d'être correct. "En fait, les deux ont exactement raison.
curiousguy
@curiousguy. C'est ainsi que Java procède, car les vérifications sont appliquées au moment de la compilation. C ++ sont des contrôles d'exécution. Donc: Guarantee that functions will only throw listed exceptions (possibly none). Pas vrai. Cela garantit seulement que si la fonction lève ces exceptions, l'application se terminera. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownJe ne peux pas faire ça. Parce que je viens de souligner que vous ne pouvez pas garantir que l'exception ne sera pas levée.
Martin York
1
@LokiAstari "C'est ainsi que Java le fait car les vérifications sont appliquées au moment de la compilation_" La spécification d'exception Java est une expérience qui a échoué. Java n'a pas la spécification d'exception la plus utile: throw()(ne lance pas). " Cela garantit seulement que si la fonction lève ces exceptions, l'application se terminera " Non "pas vrai" signifie vrai. Il n'y a aucune garantie en C ++ qu'une fonction n'appelle terminate()jamais. " Parce que je viens de signaler que vous ne pouvez pas garantir que l'exception ne sera pas levée " Vous garantissez que la fonction ne sera pas lancée. C'est exactement ce dont vous avez besoin.
curiousguy
8

gcc émettra des avertissements lorsque vous enfreignez les spécifications d'exception. Ce que je fais est d'utiliser des macros pour utiliser les spécifications d'exception uniquement en mode "lint" compilez expressément pour vérifier que les exceptions sont conformes à ma documentation.

Jérémie
la source
7

Le seul spécificateur d'exception utile est "throw ()", comme dans "don't throw".

Harold Ekstrom
la source
2
Pouvez-vous s'il vous plaît ajouter une raison pour laquelle il est utile?
buti-oxa
2
pourquoi n'est-ce pas utile? il n'y a rien de plus utile que de savoir qu'une fonction ne va pas commencer à lancer des exceptions à gauche, à droite et au centre.
Matt Joiner
1
Veuillez consulter la discussion sur Herb Sutter mentionnée dans la réponse de Michael Burr pour une explication détaillée.
Harold Ekstrom
4

Les spécifications d'exception ne sont pas des outils très utiles en C ++. Cependant, il / y a / une bonne utilisation pour eux, s'ils sont combinés avec std ::xpected.

Ce que je fais dans certains projets, c'est du code avec des spécifications d'exception, puis j'appelle set_unexpected () avec une fonction qui lèvera une exception spéciale de ma propre conception. Cette exception, lors de la construction, obtient une trace (d'une manière spécifique à la plate-forme) et est dérivée de std :: bad_exception (pour lui permettre d'être propagée si vous le souhaitez). Si cela provoque un appel terminate (), comme il le fait habituellement, la trace arrière est imprimée par what () (ainsi que l'exception d'origine qui l'a causé; pas difficile à trouver) et donc j'obtiens des informations sur l'emplacement de mon contrat. violé, par exemple quelle exception de bibliothèque inattendue a été levée.

Si je fais cela, je n'autorise jamais la propagation des exceptions de bibliothèque (sauf celles std) et je dérive toutes mes exceptions de std :: exception. Si une bibliothèque décide de lancer, je vais attraper et convertir dans ma propre hiérarchie, ce qui me permet de toujours contrôler le code. Les fonctions modèles qui appellent des fonctions dépendantes devraient éviter les spécifications d'exception pour des raisons évidentes; mais il est rare d'avoir une interface de fonction basée sur des modèles avec du code de bibliothèque de toute façon (et peu de bibliothèques utilisent vraiment des modèles de manière utile).

coppro
la source
3

Si vous écrivez du code qui sera utilisé par des personnes qui préfèrent regarder la déclaration de fonction plutôt que tout commentaire autour d'elle, alors une spécification leur dira quelles exceptions ils pourraient vouloir intercepter.

Sinon, je ne trouve pas particulièrement utile d'utiliser autre chose que throw()d'indiquer que cela ne lève aucune exception.

Branan
la source
3

Non. Si vous les utilisez et qu'une exception est levée que vous n'avez pas spécifiée, que ce soit par votre code ou par le code appelé par votre code, le comportement par défaut est de mettre fin rapidement à votre programme.

De plus, je pense que leur utilisation a été déconseillée dans les versions actuelles de la norme C ++ 0x.

Ferruccio
la source
2

En général, je n'utiliserais pas de spécificateurs d'exception. Cependant, dans les cas où une autre exception venait de la fonction en question que le programme serait définitivement incapable de corriger , alors cela peut être utile. Dans tous les cas, assurez-vous de documenter clairement les exceptions qui peuvent être attendues de cette fonction.

Oui, le comportement attendu d'une exception non spécifiée lancée à partir d'une fonction avec des spécificateurs d'exception est d'appeler terminate ().

Je noterai également que Scott Meyers aborde ce sujet dans un C ++ plus efficace. Son C ++ efficace et son C ++ plus efficace sont des livres fortement recommandés.

Kris Kumler
la source
2

Oui, si vous aimez la documentation interne. Ou peut-être écrire une bibliothèque que d'autres utiliseront, afin qu'ils puissent dire ce qui se passe sans consulter la documentation. Lancer ou ne pas lancer peut être considéré comme faisant partie de l'API, presque comme la valeur de retour.

Je suis d'accord, ils ne sont pas vraiment utiles pour appliquer l'exactitude du style Java dans le compilateur, mais c'est mieux que rien ou des commentaires aléatoires.

utilisateur10392
la source
2

Ils peuvent être utiles pour les tests unitaires afin que, lors de l'écriture des tests, vous sachiez à quoi s'attendre de la fonction à lancer en cas d'échec, mais il n'y a aucune application qui les entoure dans le compilateur. Je pense que ce sont du code supplémentaire qui n'est pas nécessaire en C ++. Quel que soit votre choix, tout ce dont vous devez être sûr, c'est que vous suivez la même norme de codage dans tout le projet et les membres de l'équipe afin que votre code reste lisible.

Impair
la source
0

Extrait de l'article:

http://www.boost.org/community/exception_safety.html

«Il est bien connu qu'il est impossible d'écrire un conteneur générique sans exception.» Cette affirmation est souvent entendue en référence à un article de Tom Cargill [4] dans lequel il explore le problème de la sécurité des exceptions pour un modèle de pile générique. Dans son article, Cargill soulève de nombreuses questions utiles, mais ne parvient malheureusement pas à présenter une solution à son problème.1 Il conclut en suggérant qu'une solution n'est peut-être pas possible. Malheureusement, son article a été lu par beaucoup comme une «preuve» de cette spéculation. Depuis sa publication, il y a eu de nombreux exemples de composants génériques sans exception, parmi lesquels les conteneurs de bibliothèque standard C ++.

Et en effet, je peux penser à des moyens de sécuriser les exceptions des classes de modèles. À moins que vous n'ayez pas le contrôle sur toutes les sous-classes, vous pouvez avoir un problème de toute façon. Pour ce faire, vous pouvez créer des typedefs dans vos classes qui définissent les exceptions levées par diverses classes de modèles. Cela pense que le problème est comme toujours de l'aborder par la suite plutôt que de le concevoir dès le départ, et je pense que c'est cette surcharge qui est le véritable obstacle.

Marius
la source
-2

Spécifications d'exception = nul, demandez à tout développeur Java de plus de 30 ans

Greg Dean
la source
8
les programmeurs java de plus de 30 ans devraient se sentir mal de ne pas pouvoir faire face à C, ils n'ont aucune excuse pour utiliser java.
Matt Joiner