web-dev-qa-db-fra.com

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

En C++, vous pouvez spécifier qu'une fonction peut ou non lever une exception en utilisant 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 en raison des éléments suivants:

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

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

119
1800 INFORMATION

Non.

Voici plusieurs exemples pourquoi:

  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 lever, le passage de paramètres peut lever 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 en

    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 voulez dire, lorsque vous écrivez des fonctions virtuelles.

  3. Code hérité

    Lorsque vous écrivez du code qui s'appuie sur une autre bibliothèque, vous ne savez pas vraiment ce que cela peut faire lorsque quelque chose tourne mal.

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

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

  4. Écrivez du code qui renvoie des erreurs courantes et les lance à 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.

93
Christopher

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

Voir Herb Sutter's "A Pragmatic Look at Exception Specifications" .

42
Michael Burr

Je pense que la convention standard sauf (pour C++)
Les spécificateurs d'exceptions étaient une expérience dans la norme C++ qui a échoué la plupart du temps.
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 d'une documentation sur la façon 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 autrement 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 échappe à 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 fournie 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 une exception sera levée et l'état des objets ne changera pas.

Aucune garantie de lancer:

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

14
Martin York

gcc émettra des avertissements lorsque vous violerez les spécifications d'exception. Ce que je fais est d'utiliser des macros pour utiliser les spécifications d'exception uniquement dans un mode "lint" compilé expressément pour vérifier que les exceptions correspondent à ma documentation.

8
Jeremy

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

7
Harold Ekstrom

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

Dans certains projets, je fais 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). S'il provoque un appel terminate (), comme c'est généralement le cas, la trace est imprimée par quoi () (ainsi que l'exception d'origine qui l'a provoqué; pas trop difficile à trouver) et donc j'obtiens des informations sur l'endroit où mon contrat était violée, telle que l'exception de bibliothèque inattendue qui a été levée.

Si je fais cela, je n'autorise jamais la propagation des exceptions de bibliothèque (à l'exception des exceptions std) et 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 un modèle avec le code de bibliothèque de toute façon (et peu de bibliothèques utilisent vraiment des modèles de manière utile).

4
coppro

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

En outre, je pense que leur utilisation est déconseillée dans les versions actuelles de la norme C++ 0x.

4
Ferruccio

Si vous écrivez du code qui sera utilisé par des personnes qui préfèrent regarder la déclaration de fonction que les commentaires qui l'entourent, une spécification leur indiquera les exceptions qu'ils pourraient vouloir intercepter.

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

3
Branan

Une spécification "throw ()" permet au compilateur d'effectuer certaines optimisations lors de l'analyse de flux de code s'il sait que la fonction ne lèvera jamais d'exception (ou du moins promet de ne jamais lever d'exception). Larry Osterman en parle brièvement ici:

http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx

3
TheJuice

En général, je n'utiliserais pas de spécificateurs d'exceptions. Cependant, dans le cas où si 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 quelles exceptions peuvent être attendues de cette fonction.

Oui, le comportement attendu d'une exception non spécifiée levé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 C++ plus efficace. Son C++ efficace et C++ plus efficace sont des livres fortement recommandés.

2
Kris Kumler

Oui, si vous êtes dans la documentation interne. Ou peut-être en écrivant 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 Java dans le compilateur, mais c'est mieux que rien ou des commentaires aléatoires.

2
user10392

Ils peuvent être utiles pour les tests unitaires afin que lors de l'écriture des tests, vous sachiez à quoi s'attendre de la fonction 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, vous devez être sûr de suivre la même norme de codage à travers le projet et les membres de l'équipe afin que votre code reste lisible.

2
Odd

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." 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 pourrait ne pas être possible. Malheureusement, son article a été lu par beaucoup comme une "preuve" de cette spéculation. Depuis sa publication, il existe de nombreux exemples de composants génériques sûrs pour les exceptions, parmi lesquels les conteneurs de bibliothèque standard C++.

Et en effet, je peux penser à des moyens de sécuriser les classes de modèles. À moins que vous n'ayez pas le contrôle sur toutes les sous-classes, vous pouvez quand même avoir un problème. 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 toujours de le résoudre par la suite plutôt que de le concevoir dès le début, et je pense que c'est cette surcharge qui est le véritable obstacle.

0
Marius