web-dev-qa-db-fra.com

Lancer le mot clé dans la signature de la fonction

Quelle est la raison technique pour laquelle il est considéré comme une mauvaise pratique d’utiliser le mot clé C++ throw dans une signature de fonction?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}
184
Konstantin

Non, ce n'est pas considéré comme une bonne pratique. Au contraire, cela est généralement considéré comme une mauvaise idée.

http://www.gotw.ca/publications/mill22.htm explique beaucoup plus en détail pourquoi, mais le problème réside en partie dans le fait que le compilateur est incapable de faire respecter cela, il doit donc l'être vérifié au moment de l'exécution, ce qui est généralement indésirable. Et ce n'est pas bien supporté dans tous les cas. (MSVC ignore les spécifications des exceptions, à l'exception de throw (), qu'il interprète comme une garantie qu'aucune exception ne sera levée.

115
jalf

Jalf y est déjà lié, mais GOTW explique assez bien pourquoi les spécifications d'exception ne sont pas aussi utiles qu'on pourrait l'espérer:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

Les commentaires sont-ils corrects? Pas assez. Gunc() peut en effet lancer quelque chose, et Hunc() peut également lancer autre chose que A ou B! Le compilateur garantit simplement de les battre sans raison s'ils le font… oh, et de battre votre programme aussi, la plupart du temps.

En résumé, vous allez probablement vous retrouver avec un appel à terminate() et votre programme mourant d'une mort rapide mais douloureuse.

La conclusion de GOTW est la suivante:

Voici donc ce qui semble être le meilleur conseil que nous ayons appris en tant que communauté:

  • Moral # 1: N'écrivez jamais une spécification d'exception.
  • Moral # 2: Sauf peut-être vide, mais si j'étais vous, j'éviterais même cela.
50
sth

Pour ajouter un peu plus de valeur à toutes les autres réponses à cette question, il faut investir quelques minutes dans la question: Quelle est la sortie du code suivant?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

Réponse: Comme indiqué ici , le programme appelle std::terminate() et, par conséquent, aucun des gestionnaires d'exceptions ne sera appelé.

Détails: La première fonction my_unexpected() est appelée, mais comme elle ne renvoie pas un type d'exception correspondant pour le prototype de fonction throw_exception(), à la fin, std::terminate() est appelé. Donc, la sortie complète ressemble à ceci:

utilisateur @ utilisateur: ~/tmp $ g ++ -o except.test except.test.cpp
utilisateur @ utilisateur: ~/tmp $ ./except.test
Bien - c'était inattendu
se termine après avoir lancé une instance de 'int'
Aborted (noyau vidé)

28
John Doe

Le seul effet pratique du spécificateur de jet est que si votre fonction jette une chose différente de myExc, std::unexpected sera appelé (au lieu du mécanisme normal des exceptions non gérées).

Pour documenter le type d'exceptions qu'une fonction peut générer, je le fais généralement:

bool
some_func() /* throw (myExc) */ {
}
11
Paolo Tedesco

Pendant que je cherchais des informations sur cette spécification de lancer, j'ai jeté un coup d'œil à cet article: --- (http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx =)

J'en reproduis une partie ici aussi, de sorte qu'il puisse être utilisé à l'avenir, que le lien ci-dessus fonctionne ou non.

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

Lorsque le compilateur voit cela, avec l'attribut "throw ()", il peut optimiser complètement la variable "bar", car il sait qu'il n'est pas possible de lever une exception à partir de MethodThatCannotThrow (). Sans l'attribut throw (), le compilateur doit créer la variable "bar", car si MethodThatCannotThrow lève une exception, le gestionnaire d'exceptions peut/dépendra de la valeur de la variable bar.

En outre, les outils d’analyse de code source tels que prefast peuvent (et utiliseront) l’annotation throw () pour améliorer leurs capacités de détection des erreurs - par exemple, si vous avez un try/catch et que toutes les fonctions que vous appelez sont marquées comme throw (), vous n'avez pas besoin d'essayer/attraper (oui, cela pose un problème si vous appelez plus tard une fonction qui pourrait lancer).

9
Pratik Singhal

Lorsque les spécifications du lancer ont été ajoutées au langage, c'était avec les meilleures intentions, mais la pratique a prouvé une approche plus pratique.

Avec C++, ma règle générale est d’utiliser uniquement les spécifications de projection pour indiquer qu’une méthode ne peut pas lancer. C'est une garantie forte. Sinon, supposons qu'il puisse jeter quoi que ce soit.

8
Greg D

Une spécification no throw sur une fonction en ligne qui ne retourne qu’une variable membre et ne peut éventuellement exclure des exceptions peut être utilisée par certains compilateurs pour faire des pessimisations -up Word pour l'opposé des optimisations) pouvant avoir un effet néfaste sur les performances. Ceci est décrit dans la littérature Boost: Exception-specification

Avec certains compilateurs , une spécification no-throw sur des fonctions non inline peut être bénéfique si les optimisations correctes sont effectuées et que l'utilisation de cette fonction a un impact sur les performances dans une manière que cela le justifie.

Pour moi, il me semble que l’utiliser ou non est un appel lancé par un œil très critique dans le cadre d’un effort d’optimisation des performances, éventuellement à l'aide d'outils de profilage.

Une citation du lien ci-dessus pour ceux qui sont pressés (contient un exemple d'effets indésirables inattendus de spécifier jeter sur une fonction inline d'un compilateur naïf):

Justification de la spécification d'exception

Les spécifications d'exception [ISO 15.4] sont parfois codées pour indiquer quelles exceptions peuvent être levées ou parce que le programmeur espère qu'elles amélioreront les performances. Mais considérons le membre suivant d'un pointeur intelligent:

T & opérateur * () const throw () {return * ptr; }

Cette fonction n'appelle pas d'autres fonctions; il manipule uniquement les types de données fondamentaux tels que les pointeurs. Par conséquent, aucun comportement d'exécution de la spécification d'exception ne peut jamais être appelé. La fonction est complètement exposée au compilateur; En effet, il est déclaré inline. Par conséquent, un compilateur intelligent peut facilement déduire que les fonctions sont incapables de générer des exceptions et effectuer les mêmes optimisations qu’il aurait effectuées sur la base de la spécification d’exception vide. Un compilateur "muet", cependant, peut faire toutes sortes de pessimisations.

Par exemple, certains compilateurs désactivent l'inline s'il existe une spécification d'exception. Certains compilateurs ajoutent des blocs try/catch. De telles pessimisations peuvent être un désastre de performances qui rend le code inutilisable dans des applications pratiques.

Bien qu’initialement attrayante, une spécification d’exception tend à avoir des conséquences qui nécessitent une réflexion très minutieuse. Le plus gros problème avec les spécifications d'exception est que les programmeurs les utilisent comme si elles avaient l'effet que le programmeur voudrait, au lieu de l'effet qu'ils ont réellement.

Une fonction non inline est le seul endroit où une spécification d'exception "ne jette rien" peut présenter certains avantages avec certains compilateurs.

3
Pablo Adames