Quand dois-je vraiment utiliser noexcept?

le mot-clé noexcept peut être appliqué de façon appropriée à de nombreuses signatures de fonctions, mais je ne sais pas quand je devrais envisager de l'utiliser dans la pratique. Sur la base de ce que j'ai lu jusqu'à présent, l'ajout de dernière minute de noexcept semble répondre à certaines questions importantes qui se posent lorsque les constructeurs de mouvements lancent. Cependant, je ne suis toujours pas en mesure de fournir des réponses satisfaisantes à certaines questions pratiques qui m'ont amené à lire plus sur noexcept en premier lieu.

  1. il y a beaucoup d'exemples de fonctions que je sais ne jetteront jamais, mais pour lesquelles le compilateur ne peut pas le déterminer seul. Dois-je ajouter noexcept à la déclaration de fonction dans tous ces cas?

    avoir à penser si oui ou non je dois ajouter noexcept après chaque déclaration de fonction réduirait considérablement la productivité des programmeurs (et franchement, serait une douleur dans le cul). Pour quelles situations devrais-je faire plus attention à l'utilisation de noexcept , et pour quelles situations puis-je m'en tirer avec le noexcept(false) implicite ?

  2. Quand puis-je raisonnablement m'attendre à observer une amélioration de la performance après avoir utilisé noexcept ? En particulier, donnez un exemple de code pour lequel un compilateur C++ est capable de générer un meilleur code machine après l'ajout de noexcept .

    Personnellement, je me soucie de noexcept en raison de la liberté accrue fournie au compilateur d'appliquer en toute sécurité certains types d'optimisations. Est-ce que les compilateurs modernes profitent de noexcept de cette façon? Dans la négative, puis-je m'attendre à ce que certains d'entre eux le fassent dans un proche avenir?

373
demandé sur void-pointer 2012-05-28 20:29:09

9 réponses

je pense qu'il est trop tôt pour donner une réponse aux" meilleures pratiques " à cet égard, car il n'y a pas eu assez de temps pour l'utiliser dans la pratique. Si cette question était posée à propos des spécificateurs de jet juste après qu'ils soient sortis alors les réponses seraient très différentes à aujourd'hui.

avoir à penser si oui ou non je dois ajouter noexcept après chaque déclaration de fonction réduirait considérablement la productivité de programmeur (et franchement, serait une douleur).

Eh bien l'utiliser quand il est évident que la fonction ne lancera jamais.

Quand puis-je raisonnablement m'attendre à observer une amélioration de la performance après avoir utilisé noexcept ? [...] Personnellement, je me soucie de noexcept en raison de la liberté accrue fournie au compilateur d'appliquer en toute sécurité certains types d'optimisations.

il semble que les plus grands gains d'optimisation sont de l'utilisateur les optimisations, pas de compilateur en raison de la possibilité de vérifier noexcept et la surcharge. La plupart des compilateurs suivent une méthode de manipulation sans pénalité si vous ne lancez pas d'exception, donc je doute qu'elle change beaucoup (ou quoi que ce soit) au niveau du code machine de votre code, bien que peut-être réduire la taille binaire en supprimant le code de manipulation.

en utilisant noexcept dans le big 4 (constructeurs, affectation, pas destructeurs car ils sont déjà noexcept ) causera probablement les meilleures améliorations en tant que contrôles noexcept sont "courantes" dans le code de modèle tel que dans les conteneurs std. Par exemple, std::vector n'utilisera pas le mouvement de votre classe à moins qu'il ne soit marqué noexcept (ou le compilateur peut en déduire autrement).

150
répondu Pubby 2018-05-19 06:06:41

comme je répète sans cesse ces jours-ci: sémantique première .

ajouter noexcept , noexcept(true) et noexcept(false) est avant tout une question de sémantique. Il conditionne seulement incidemment un certain nombre d'optimisations possibles.

en tant que code de lecture de programmeur, la présence de noexcept est similaire à celle de const : il m'aide à mieux grok ce qui peut ou ne peut pas se produire. Par conséquent, il vaut la peine de dépenser certains de temps à penser à si oui ou non vous savez si la fonction jeter. Pour rappel, n'importe quelle sorte d'allocation de mémoire dynamique peut jeter.


Ok, maintenant aux optimisations possibles.

les optimisations les plus évidentes sont effectivement effectuées dans les bibliothèques. C++11 fournit un certain nombre de traits qui permettent de savoir si une fonction est noexcept ou non, et L'implémentation de la bibliothèque Standard elle-même utilisera ces traits pour favoriser les opérations noexcept sur les objets définis par l'utilisateur qu'ils manipulent, si possible. Telles que sémantique de déplacement .

le compilateur peut seulement raser un peu de graisse (peut-être) de l'exception traitant des données, parce qu'il a pour tenir compte du fait que vous avez peut-être menti. Si une fonction marquée noexcept lance, alors std::terminate est appelé.

cette sémantique était choisi pour deux raisons:

  • bénéficiant immédiatement de noexcept même si les dépendances ne l'utilisent pas déjà (rétrocompatibilité)
  • permettant la spécification de noexcept lors de l'appel de fonctions qui peuvent théoriquement jeter, mais ne sont pas attendus pour les arguments donnés
112
répondu Matthieu M. 2017-06-06 17:00:49

Cela fait en fait une (potentiellement) énorme différence pour l'optimiseur dans le compilateur. Les compilateurs ont en fait cette fonctionnalité depuis des années via l'instruction empty throw() après une définition de fonction, ainsi que des extensions de propriété. Je peux vous assurer que les compilateurs modernes profitent de cette connaissance pour générer un meilleur code.

presque chaque optimisation dans le compilateur utilise quelque chose appelé un "graphique de flux" d'une fonction pour raisonner sur ce qui est juridique. Un graphique de flux se compose de ce qui sont généralement appelés "blocs" de la fonction (zones de code qui ont une entrée simple et une sortie simple) et les bords entre les blocs pour indiquer où le flux peut sauter. Noexcept modifie le diagramme de flux.

Vous avez demandé un exemple précis. Considérez ce code:

void foo(int x) {
    try {
        bar();
        x = 5;
        // other stuff which doesn't modify x, but might throw
    } catch(...) {
        // don't modify x
    }

    baz(x); // or other statement using x
}

le graphique de flux pour cette fonction est différent si bar est étiqueté noexcept (il n'y a aucun moyen pour l'exécution de sauter entre la fin de bar et l'instruction catch). Lorsque étiqueté comme noexcept , le compilateur est certain que la valeur de x est 5 pendant la fonction baz - le bloc x=5 est dit" dominer "le bloc baz(x) sans le bord de bar() à la déclaration de capture. Il peut alors faire quelque chose appelé "propagation constante" pour générer du code plus efficace. Ici, si baz est inlined, les déclarations utilisant x peuvent aussi contenir des constantes et alors ce qui était une évaluation d'exécution peut être tourné dans une évaluation de compilation, etc.

quoi qu'il en soit, brève réponse: noexcept permet au compilateur de générer un graphique de flux plus serré, et le graphique de flux est utilisé pour raisonner sur toutes sortes d'optimisations de compilateurs communs. Pour un compilateur, les annotations utilisateur de cette nature sont impressionnantes. Le compilateur va essayer de comprendre ce truc, mais il ne peut généralement pas (la fonction en question peut être dans un autre fichier objet non visible par le compilateur ou utiliser de façon transitoire une fonction qui n'est pas visible), ou quand il le fait, il y a une exception triviale qui pourrait être jetée dont vous n'êtes même pas conscient, donc il ne peut pas l'étiqueter implicitement comme noexcept (allouer de la mémoire pourrait jeter bad_alloc, par exemple).

62
répondu Terry Mahaffey 2018-05-19 06:56:00

noexcept peut considérablement améliorer la performance de certaines opérations. Cela ne se produit pas au niveau de la génération du code machine par le compilateur, mais en sélectionnant l'algorithme le plus efficace: comme d'autres l'ont mentionné, vous faites cette sélection en utilisant la fonction std::move_if_noexcept . Par exemple, la croissance de std::vector (par exemple, quand nous appelons reserve ) doit fournir une forte exception-garantie de sécurité. S'il sait que le constructeur de mouvements de T ne lance pas, il peut juste élément. Sinon, il doit copier tous les T s. Cela a été décrit en détail dans ce post .

42
répondu Andrzej 2012-09-24 07:31:45

Quand puis-je de façon réaliste sauf observer une amélioration de la performance après avoir utilisé noexcept ? En particulier, donnez un exemple de code pour lequel un compilateur C++ est capable de générer un meilleur code machine après l'ajout de noexcept.

jamais? N'est jamais un moment? Jamais.

noexcept est pour compilateur optimisations de performance de la même manière que const est pour optimisation des performances des compilateurs. C'est, presque jamais.

noexcept est principalement utilisé pour permettre à "vous" de détecter au moment de la compilation si une fonction peut lancer une exception. Rappelez-vous: la plupart des compilateurs n'émettent pas de code spécial pour les exceptions à moins qu'il ne jette quelque chose. Donc noexcept n'est pas une question de donner le compilateur conseils sur la façon d'optimiser une fonction tellement comme donnant vous conseils sur la façon d'utiliser une fonction.

Modèles comme move_if_noexcept détecte si le constructeur de déplacement est défini avec noexcept et sera de retour un const& au lieu de && du type si il ne l'est pas. C'est une façon de dire à déplacer si il est très sûr à le faire.

En général, vous devez utiliser noexcept quand vous pensez qu'il sera réellement utile pour le faire. Code prennent des chemins différents si is_nothrow_constructible est vrai pour ce type. Si vous utilisez le code cela fera cela, alors n'hésitez pas à noexcept les constructeurs appropriés.

en bref: utilisez-le pour les constructeurs de mouvements et les constructions similaires, mais ne vous sentez pas comme si vous avez à faire des noix avec elle.

27
répondu Nicol Bolas 2012-05-28 17:31:05
  1. il y a beaucoup d'exemples de fonctions que je sais ne lanceront jamais, mais pour lesquelles le compilateur ne peut pas le déterminer lui-même. Dois-je ajouter noexcependant à la déclaration de fonction dans tous ces cas?

noexcept est délicat, car il fait partie de l'interface de fonctions. En particulier, si vous écrivez une bibliothèque, votre code client peut dépendre de la propriété noexcept . Il peut être difficile de changer plus tard, car vous risquez de casser le code existant. Cela pourrait être moins préoccupant lorsque vous mettez en œuvre un code qui n'est utilisé que par votre application.

si vous avez une fonction qui ne peut pas lancer, demandez-vous si elle aimera rester noexcept ou si cela restreindrait les implémentations futures? Par exemple, vous pourriez vouloir introduire une vérification d'erreur des arguments illégaux en lançant des exceptions (par exemple, pour les tests unitaires), ou vous pourriez dépendre d'un autre code de bibliothèque qui pourrait modifier sa spécification d'exception. Dans ce cas, il est plus sûr d'être conservateur et d'omettre noexcept .

d'autre part, si vous êtes sûr que la fonction ne devrait jamais jeter et il est correct qu'il fait partie de la spécification, vous devez le déclarer noexcept . Cependant, gardez à l'esprit que le compilateur ne sera pas en mesure de détecter des violations de noexcept si votre mise en œuvre change.

  1. Pour quelles situations devrais-je faire plus attention à l'utilisation de noexcept, et pour quelles situations puis-je m'en tirer avec le noexcept(faux) implicite?

il y a quatre catégories de fonctions sur lesquelles vous devriez vous concentrer parce qu'elles auront probablement le plus grand impact:

  1. les opérations de déplacement (move opérateur d'affectation et des constructeurs de déplacement)
  2. opérations de swap de
  3. désallocateurs de mémoire (opérateur Supprimer, opérateur Supprimer[])
  4. destructeurs (bien qu'ils soient implicitement noexcept(true) sauf si vous les faites noexcept(false) )

ces fonctions devraient généralement être noexcept , et il est très probable que les implémentations de bibliothèques peuvent utiliser la propriété noexcept . Par exemple, std::vector peut utiliser des opérations de mouvement sans lancer sans sacrifier de fortes garanties d'exception. Dans le cas contraire, il devra revenir à la copie d'éléments (comme il l'a fait dans C++98).

ce type d'optimisation est au niveau algorithmique et ne repose pas sur des optimisations de compilateurs. Il peut avoir un impact significatif, surtout si les éléments sont coûteux à copier.

  1. Quand puis-je raisonnablement m'attendre à observer une amélioration de la performance après avoir utilisé noexcept? En particulier, donner un exemple de code en C++ le compilateur est capable de générer un meilleur code machine après l'ajout de noexcept.

l'avantage de noexcept contre aucune spécification d'exception ou throw() est que la norme permet aux compilateurs plus de liberté quand il s'agit de la pile déroulement. Même dans le cas throw() , le compilateur doit complètement décompresser la pile (et il doit le faire dans l'ordre exact inverse des constructions de l'objet).

dans l'affaire noexcept , en revanche, elle n'est pas tenue de le faire. Il n'est pas nécessaire que la pile doit être dénoué (mais le compilateur est encore autorisé à le faire). Cette liberté permet une optimisation supplémentaire du code, car elle diminue la charge de toujours être en mesure de décompresser la pile.

La question connexe à propos de noexcept, le déroulement de pile et de la performance va dans plus de détails à propos de la surcharge lorsque le déroulement de pile est requis.

je recommande également Scott Meyers Livre" efficace moderne C++"," Point 14: déclarer les fonctions noexcept si elles ne seront pas émettre des exceptions " pour une lecture plus approfondie.

17
répondu Philipp Claßen 2017-05-23 11:55:07

De Bjarne mots:

lorsque la résiliation est une réponse acceptable, une exception non justifiée va y arriver parce qu'il se transforme en un appel d'arrivée() (§13.5.2.5). En outre, un spécificateur noexcept (§13.5.1.1) peut faire que le désir explicite.

les systèmes à tolérance de pannes réussis sont à plusieurs niveaux. Chaque niveau fonctionne avec autant d'erreurs que possible sans trop se tortiller et laisse le reste de la hausse des niveau. Les Exceptions appuient ce point de vue. Outre, terminate() confirme ce point de vue en fournissant une échappatoire si le exception-le mécanisme de manipulation lui-même est corrompu ou s'il a été utilisé de manière incomplète, laissant ainsi les exceptions en suspens. Pareillement, noexcept fournit une évasion simple pour les erreurs où essayer de récupérer semble infaisable.

 double compute(double x) noexcept;   {       
     string s = "Courtney and Anya"; 
     vector<double> tmp(10);      
     // ...   
 }

le constructeur vectoriel peut ne pas acquérir mémoire de ses dix doubles et lancez un std::bad_alloc . Dans ce cas, le programme s'arrête. Il se termine sans condition en invoquant std::terminate() (§30.4.1.3). Il n'invoque pas les destructeurs des fonctions d'appel. Il est mise en œuvre-défini si les destructeurs à partir de portées entre les Les Termes throw et noexcept (par exemple, for s in compute()) sont invoqués. Le le programme est sur le point de se terminer, donc nous ne devrions pas dépendre de objet de toute façon. By en ajoutant un spécificateur noexcept , nous indiquons que notre le code n'a pas été écrit pour faire face à un lancer.

12
répondu Saurav Sahu 2017-08-03 07:41:39

il y a beaucoup d'exemples de fonctions que je sais ne lanceront jamais, mais pour lesquelles le compilateur ne peut pas le déterminer lui-même. Dois-je ajouter noexcependant à la déclaration de fonction dans tous ces cas?

quand vous dites "je sais qu'ils ne jetteront jamais", vous voulez dire en examinant la mise en œuvre de la fonction vous savez que la fonction ne jettera pas. Je pense que cette approche est à l'intérieur.

il vaut mieux examiner si une fonction peut jeter exceptions pour faire partie de la conception de la fonction: aussi important que la liste d'arguments et si une méthode est un mutateur (... const ). Déclarer que" cette fonction ne fait jamais d'exception " est une contrainte sur la mise en œuvre. Omettre cela ne signifie pas que la fonction pourrait lancer des exceptions; cela signifie que la version actuelle de la fonction et toutes les versions futures peuvent lancer des exceptions. C'est un contrainte qui rend la mise en œuvre plus difficile. Mais certaines méthodes doivent avoir la contrainte d'être pratiquement utiles; plus important encore, de sorte qu'ils peuvent être appelés à partir des destructeurs, mais aussi pour la mise en œuvre du code "roll-back" dans les méthodes qui fournissent la garantie d'exception forte.

10
répondu Raedwald 2012-05-30 11:57:05

étant donné le fait que le nombre de règles pour travailler witch c++ est en augmentation constante et que nous avons vécu heureux sans exception pendant des années... je pense qu'à moins que quelqu'un donne des raisons de tuer pourquoi l'utiliser, personne ne le fera... ou au moins tous les jours c++ développeurs

-2
répondu Martin Kosicky 2018-07-30 16:53:59