Quand devrais-je vraiment utiliser noexcept?


509

Le noexceptmot-clé peut être appliqué de manière appropriée à de nombreuses signatures de fonction, 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 noexceptsemble résoudre certains problèmes importants qui surviennent 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é à en savoir plus noexcepten premier lieu.

  1. Il existe de nombreux exemples de fonctions que je sais ne lanceront jamais, mais pour lesquelles le compilateur ne peut pas le déterminer par lui-même. Dois-je joindre noexceptà la déclaration de fonction dans tous ces cas?

    Devoir penser à savoir si je dois ou non ajouter noexceptaprès chaque déclaration de fonction réduirait considérablement la productivité du programmeur (et franchement, ce serait pénible). Pour quelles situations devrais-je être plus prudent quant à l'utilisation de noexcept, et pour quelles situations puis-je m'en tirer avec les implicites noexcept(false)?

  2. Quand puis-je m'attendre de manière réaliste à observer une amélioration des performances après utilisation 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 noexceptde la liberté accrue accordée au compilateur pour appliquer en toute sécurité certains types d'optimisations. Les compilateurs modernes profitent-ils de noexceptcette manière? Sinon, puis-je m'attendre à ce que certains le fassent dans un proche avenir?


40
Le code qui utilise move_if_nothrow(ou whatchamacallit) verra une amélioration des performances s'il y a un ctor de mouvement sans exception.
R. Martinho Fernandes


5
Ça l'est move_if_noexcept.
Nikos

Réponses:


180

Je pense qu'il est trop tôt pour donner une réponse aux "meilleures pratiques" car il n'y a pas eu assez de temps pour l'utiliser dans la pratique. Si cela était demandé à propos des lanceurs de spécification juste après leur sortie, les réponses seraient très différentes de celles d'aujourd'hui.

Devoir penser à savoir si je dois ou non ajouter noexceptaprès chaque déclaration de fonction réduirait considérablement la productivité du programmeur (et franchement, ce serait pénible).

Eh bien, utilisez-le quand il est évident que la fonction ne lancera jamais.

Quand puis-je m'attendre de manière réaliste à observer une amélioration des performances après utilisation noexcept? [...] Personnellement, je me soucie noexceptde la liberté accrue accordée au compilateur pour appliquer en toute sécurité certains types d'optimisations.

Il semble que les gains d'optimisation les plus importants proviennent des optimisations des utilisateurs et non des compilateurs en raison de la possibilité de les vérifier noexceptet de les surcharger. La plupart des compilateurs suivent une méthode de gestion des exceptions sans pénalité si vous ne lancez pas, donc je doute que cela changerait 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.

L'utilisation noexceptdans les quatre grands (constructeurs, affectations, et non destructeurs comme ils le sont déjà noexcept) entraînera probablement les meilleures améliorations car les noexceptvérifications sont «courantes» dans le code du modèle, comme dans les stdconteneurs. Par exemple, std::vectorn'utilisera pas le déplacement de votre classe à moins qu'il ne soit marqué noexcept(ou que le compilateur ne puisse le déduire autrement).


6
Je pense que l' std::terminateastuce obéit toujours au modèle Zero-Cost. Autrement dit, il se trouve que la plage d'instructions dans les noexceptfonctions est mappée pour appeler std::terminateif si elle throwest utilisée à la place du dérouleur de pile. Je doute donc qu'il ait plus de frais généraux que le suivi d'exceptions régulier.
Matthieu M.

4
@Klaim Voir ceci: stackoverflow.com/a/10128180/964135 En fait, il ne doit tout simplement pas être lancé, mais cela le noexceptgarantit.
Pubby

3
"Si une noexceptfonction est lancée, elle std::terminateest appelée, ce qui semble impliquer une petite surcharge" ... Non, cela devrait être implémenté en ne générant pas de tables d'exceptions pour une telle fonction, que le répartiteur d'exceptions devrait intercepter puis renflouer.
Potatoswatter

8
La gestion des exceptions @Pubby C ++ est généralement effectuée sans surcharge, à l'exception des tables de sauts qui mappent potentiellement les adresses des sites d'appel vers les points d'entrée du gestionnaire. La suppression de ces tables est aussi proche que possible de la suppression complète de la gestion des exceptions. La seule différence est la taille du fichier exécutable. Ne vaut probablement pas la peine de mentionner quoi que ce soit.
Potatoswatter

26
"Eh bien, utilisez-le quand il est évident que la fonction ne lancera jamais." Je ne suis pas d'accord. noexceptfait partie de l' interface de la fonction ; vous ne devez pas l'ajouter simplement parce que votre implémentation actuelle ne se produit pas. Je ne suis pas sûr de la bonne réponse à cette question, mais je suis tout à fait convaincu que la façon dont votre fonction se comporte aujourd'hui n'a rien à voir avec cela ...
Nemo

133

Comme je ne cesse de répéter ces jours-ci: la sémantique d'abord .

Ajouter noexcept, noexcept(true)et noexcept(false)c'est avant tout de la sémantique. Elle ne conditionne qu'incidemment un certain nombre d'optimisations possibles.

En tant que programmeur lisant du code, la présence de noexcepts'apparente à celle de const: cela m'aide à mieux comprendre ce qui peut ou non arriver. Par conséquent, il vaut la peine de passer du temps à réfléchir à savoir si vous savez si la fonction lancera. Pour rappel, tout type d'allocation dynamique de mémoire peut être lancé.


Bon, passons maintenant aux optimisations possibles.

Les optimisations les plus évidentes sont en fait effectuées dans les bibliothèques. C ++ 11 fournit un certain nombre de traits qui permettent de savoir si une fonction l'est noexceptou non, et l'implémentation de la bibliothèque standard utilisera ces traits pour favoriser les noexceptopérations sur les objets définis par l'utilisateur qu'ils manipulent, si possible. Tels que déplacer la sémantique .

Le compilateur peut seulement raser un peu de graisse (peut-être) des données de gestion des exceptions, car il doit tenir compte du fait que vous avez peut-être menti. Si une fonction marquée noexceptlance, elle std::terminateest appelée.

Cette sémantique a été choisie pour deux raisons:

  • bénéficier immédiatement de noexceptmême lorsque les dépendances ne l'utilisent pas déjà (compatibilité descendante)
  • permettant la spécification noexceptlors de l'appel de fonctions qui peuvent théoriquement lancer, mais ne sont pas censées le faire pour les arguments donnés

2
Je suis peut-être naïf, mais j'imagine qu'une fonction qui n'invoque que des noexceptfonctions n'aurait pas besoin de faire quoi que ce soit de spécial, car toute exception qui pourrait survenir se déclenche terminateavant d'atteindre ce niveau. Cela diffère grandement d'avoir à traiter et propager une bad_allocexception.

8
Oui, il est possible de définir noexcept de la manière que vous proposez, mais ce serait une fonctionnalité vraiment inutilisable. De nombreuses fonctions peuvent se lancer si certaines conditions ne sont pas remplies et vous ne pouvez pas les appeler même si vous savez que les conditions sont remplies. Par exemple, toute fonction qui peut lancer std :: invalid_argument.
tr3w

3
@MatthieuM. Un peu tard pour une réponse, mais quand même. Les fonctions marquées noexcept peuvent appeler d'autres fonctions qui peuvent lancer, la promesse est que cette fonction n'émettra pas d'exception c'est-à-dire qu'elles doivent juste gérer l'exception elles-mêmes!
Honf

4
J'ai voté pour cette réponse il y a longtemps, mais après avoir lu et réfléchi un peu plus, j'ai un commentaire / une question. "Déplacer la sémantique" est le seul exemple que j'ai jamais vu quelqu'un donner où noexceptest clairement utile / une bonne idée. Je commence à penser que la construction de déménagements, l'affectation de déménagements et l'échange sont les seuls cas qui existent ... Connaissez-vous d'autres cas?
Nemo

5
@Nemo: Dans la bibliothèque Standard, c'est peut-être le seul, mais il présente un principe qui peut être réutilisé ailleurs. Une opération de déplacement est une opération qui met temporairement un certain état dans des "limbes", et ce n'est que lorsque c'est le cas que noexceptquelqu'un peut l'utiliser en toute confiance sur une donnée qui pourrait être consultée par la suite. Je pourrais voir cette idée utilisée ailleurs, mais la bibliothèque Standard est plutôt mince en C ++ et elle n'est utilisée que pour optimiser les copies d'éléments, je pense.
Matthieu M.

77

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

Presque chaque optimisation dans le compilateur utilise quelque chose appelé "graphique de flux" d'une fonction pour raisonner sur ce qui est légal. Un graphe de flux se compose de ce que l'on appelle généralement des "blocs" de la fonction (zones de code qui ont une seule entrée et une seule sortie) et des bords entre les blocs pour indiquer où le flux peut aller. Noexcept modifie le graphique 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 de cette fonction est différent s'il barest étiqueté noexcept(il n'y a aucun moyen pour l'exécution de sauter entre la fin de barl'instruction catch). Lorsqu'il est étiqueté comme noexcept, le compilateur est certain que la valeur de x est 5 pendant la fonction baz - le bloc x = 5 est censé "dominer" le bloc baz (x) sans le bord de bar()à l'instruction catch.

Il peut alors faire quelque chose appelé "propagation constante" pour générer un code plus efficace. Ici, si baz est en ligne, les instructions utilisant x peuvent également contenir des constantes et ce qui était auparavant une évaluation d'exécution peut être transformé en une évaluation au moment de la compilation, etc.

Quoi qu'il en soit, la réponse courte: noexceptpermet 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 courantes du compilateur. Pour un compilateur, les annotations utilisateur de cette nature sont impressionnantes. Le compilateur essaiera de comprendre ce genre de choses, mais il ne le fait généralement pas (la fonction en question peut être dans un autre fichier objet non visible par le compilateur ou utiliser de manière transitoire une fonction qui n'est pas visible), ou quand elle le fait, il y a une exception triviale qui pourrait être levée et dont vous n'êtes même pas au courant, de sorte qu'elle ne peut pas implicitement la noexceptnommer (l'allocation de mémoire pourrait lancer bad_alloc, par exemple).


3
Cela fait-il réellement une différence dans la pratique? L'exemple est artificiel car rien avant nex = 5 peut lancer. Si cette partie du trybloc servait à quelque chose, le raisonnement ne tiendrait pas.
Potatoswatter

7
Je dirais que cela fait une réelle différence dans l'optimisation des fonctions qui contiennent des blocs try / catch. L'exemple que j'ai donné, bien qu'inventé, n'est pas exhaustif. Le point le plus important est que noexcept (comme l'instruction throw () avant elle) aide la compilation à générer un graphique de flux plus petit (moins d'arêtes, moins de blocs), ce qui est un élément fondamental de nombreuses optimisations qu'il fait.
Terry Mahaffey

Comment le compilateur peut-il reconnaître que le code peut lever une exception? L'accès au tableau est-il considéré comme une exception possible?
Tomas Kubes

3
@ qub1n Si le compilateur peut voir le corps de la fonction, il peut rechercher des throwinstructions explicites , ou d'autres choses comme newcela peuvent lancer. Si le compilateur ne peut pas voir le corps, il doit s'appuyer sur la présence ou l'absence de noexcept. L'accès simple aux tableaux ne génère généralement pas d'exceptions (C ++ n'a pas de vérification des limites), donc non, l'accès aux tableaux ne ferait pas à lui seul penser que la fonction lève des exceptions. (L'accès hors limite est UB, pas une exception garantie.)
cdhowie

@cdhowie " il doit s'appuyer sur la présence ou l'absence de noexcept " ou la présence throw()en pré-noexcept C ++
curiousguy

57

noexceptpeut considérablement améliorer les performances 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 effectuez cette sélection en utilisant la fonction std::move_if_noexcept. Par exemple, la croissance de std::vector(par exemple, lorsque nous appelons reserve) doit fournir une forte garantie d'exception-sécurité. S'il sait que Tle constructeur de mouvement ne lance pas, il peut simplement déplacer chaque élément. Sinon, il doit copier tous Tles par. Cela a été décrit en détail dans cet article .


4
Addendum: Cela signifie que si vous définissez des constructeurs de déplacement ou des opérateurs d'affectation de déplacement, ajoutez- noexcepty (le cas échéant)! Des fonctions de membre de déplacement définies implicitement y ont été noexceptajoutées automatiquement (le cas échéant).
mucaho

33

Quand puis-je réaliste, sauf pour observer une amélioration des performances après utilisation 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.

Euh, jamais? N'est-ce jamais un temps? Jamais.

noexceptest pour les optimisations de performances du compilateur de la même manière que constpour les optimisations de performances du compilateur. C'est presque jamais.

noexceptest principalement utilisé pour permettre à "vous" de détecter au moment de la compilation si une fonction peut lever une exception. N'oubliez pas: la plupart des compilateurs n'émettent pas de code spécial pour les exceptions à moins qu'il ne lance quelque chose. Il noexceptne s'agit donc pas de donner au compilateur des conseils sur la façon d'optimiser une fonction autant que de vous donner des conseils sur la façon d'utiliser une fonction.

Des modèles comme move_if_noexceptdétecteront si le constructeur de déplacement est défini avec noexceptet renverra un const&au lieu d'un &&du type s'il ne l'est pas. C'est une façon de dire de bouger s'il est très sûr de le faire.

En général, vous devez utiliser cette option noexceptlorsque vous pensez que cela sera réellement utile . Certains codes prendront des chemins différents si cela is_nothrow_constructibleest vrai pour ce type. Si vous utilisez du code qui le fera, n'hésitez pas à utiliser noexceptles constructeurs appropriés.

En bref: utilisez-le pour déplacer des constructeurs et des constructions similaires, mais ne vous sentez pas obligé de devenir fou avec.


13
Strictement, move_if_noexceptne retournera pas de copie, il renverra une const lvalue-reference plutôt qu'une rvalue-reference. En général, l'appelant effectuera une copie au lieu d'un déplacement, mais move_if_noexceptne fera pas la copie. Sinon, bonne explication.
Jonathan Wakely

12
+1 Jonathan. Le redimensionnement d'un vecteur, par exemple, déplacera les objets au lieu de les copier si le constructeur de déplacement l'est noexcept. Alors que "jamais" n'est pas vrai.
mfontanini

4
Je veux dire, le compilateur va générer un meilleur code dans cette situation. OP demande un exemple pour lequel le compilateur est capable de générer une application plus optimisée. Cela semble être le cas (même s'il ne s'agit pas d'une optimisation du compilateur ).
mfontanini

7
@mfontanini: Le compilateur génère uniquement un meilleur code car le compilateur est obligé de compiler un chemin de code différent . Cela ne fonctionne que parce qu'il std::vectorest écrit pour forcer le compilateur à compiler un code différent . Il ne s'agit pas que le compilateur détecte quelque chose; il s'agit du code utilisateur détectant quelque chose.
Nicol Bolas

3
Le fait est que je n'arrive pas à trouver "optimisation du compilateur" dans la citation au début de votre réponse. Comme l'a dit @ChristianRau, si le compilateur génère un code plus efficace, peu importe l'origine de cette optimisation. Après tout, le compilateur est de générer un code plus efficace, est - ce pas? PS: Je n'ai jamais dit que c'était une optimisation du compilateur, j'ai même dit "Ce n'est pas une optimisation du compilateur".
mfontanini

22

Selon les mots de Bjarne ( The C ++ Programming Language, 4th Edition , page 366):

Lorsque la terminaison est une réponse acceptable, une exception non interceptée y parviendra car elle se transforme en appel de terminate () (§13.5.2.5). De plus, un noexceptspécificateur (§13.5.1.1) peut rendre ce désir explicite.

Les systèmes à tolérance de pannes réussis sont à plusieurs niveaux. Chaque niveau fait face à autant d'erreurs que possible sans être trop déformé et laisse le reste à des niveaux supérieurs. Les exceptions prennent en charge cette vue. En outre, terminate()prend en charge cette vue en fournissant un échappement si le mécanisme de gestion des exceptions lui-même est corrompu ou s'il a été utilisé de manière incomplète, laissant ainsi les exceptions non détectées. De même, noexceptfournit une échappatoire simple pour les erreurs où tenter de récupérer semble irréalisable.

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

Le constructeur vectoriel peut ne pas acquérir de mémoire pour ses dix doubles et lancer a std::bad_alloc. Dans ce cas, le programme se termine. Il se termine inconditionnellement en invoquant std::terminate()(§30.4.1.3). Il n'invoque pas de destructeurs depuis des fonctions d'appel. Il est défini par l'implémentation si les destructeurs des étendues entre le throwet le noexcept(par exemple, pour s dans compute ()) sont appelés. Le programme est sur le point de se terminer, nous ne devons donc dépendre d'aucun objet de toute façon. En ajoutant un noexceptspécificateur, nous indiquons que notre code n'a pas été écrit pour faire face à un lancer.


2
Avez-vous une source pour ce devis?
Anton Golov

5
@AntonGolov "Le langage de programmation C ++, 4e édition" p. 366
Rusty Shackleford

1
Cela me semble comme si je devais en ajouter un à noexceptchaque fois, sauf que je veux explicitement m'occuper des exceptions. Soyons réalistes, la plupart des exceptions sont si improbables et / ou si fatales que le sauvetage n'est guère raisonnable ou possible. Par exemple, dans l'exemple cité, si l'allocation échoue, l'application ne pourra guère continuer à fonctionner correctement.
Neonit

21
  1. Il existe de nombreux exemples de fonctions que je sais ne lanceront jamais, mais pour lesquelles le compilateur ne peut pas le déterminer par lui-même. Dois-je ajouter noexcept à la déclaration de fonction dans tous ces cas?

noexceptest délicat, car il fait partie de l'interface des fonctions. Surtout, si vous écrivez une bibliothèque, votre code client peut dépendre de la noexceptpropriété. Il peut être difficile de le modifier plus tard, car vous pourriez casser le code existant. Cela peut être moins préoccupant lorsque vous implémentez du code uniquement utilisé par votre application.

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

D'un autre côté, si vous êtes sûr que la fonction ne doit jamais lancer et qu'il est correct qu'elle fasse partie de la spécification, vous devez la déclarer noexcept. Cependant, gardez à l'esprit que le compilateur ne pourra pas détecter les violations de noexceptsi votre implémentation 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 implicite (faux)?

Il existe quatre classes de fonctions sur lesquelles vous devriez vous concentrer, car elles auront probablement le plus grand impact:

  1. opérations de déplacement (opérateur d'affectation de déplacement et constructeurs de déplacement)
  2. opérations de swap
  3. désallocateurs de mémoire (suppression opérateur, suppression opérateur [])
  4. destructeurs (bien que ceux-ci le soient implicitement noexcept(true)sauf si vous les faites noexcept(false))

Ces fonctions devraient généralement l'être noexcept, et il est très probable que les implémentations de bibliothèque puissent utiliser la noexceptpropriété. Par exemple, std::vectorpeut utiliser des opérations de déplacement sans lancer sans sacrifier les garanties d'exception fortes. Sinon, il devra se replier sur la copie des éléments (comme il l'a fait en C ++ 98).

Ce type d'optimisation est au niveau algorithmique et ne dépend pas des optimisations du compilateur. Cela peut avoir un impact significatif, surtout si les éléments sont coûteux à copier.

  1. Quand puis-je m'attendre de manière réaliste à observer une amélioration des performances 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.

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

Dans le noexceptcas, en revanche, il n'est pas nécessaire de le faire. Il n'est pas nécessaire que la pile soit déroulée (mais le compilateur est toujours autorisé à le faire). Cette liberté permet une optimisation supplémentaire du code car elle réduit la surcharge de toujours pouvoir dérouler la pile.

La question connexe à propos de noexcept, du déroulement de la pile et des performances donne plus de détails sur la surcharge lorsque le déroulement de la pile est requis.

Je recommande également le livre de Scott Meyers "Effective Modern C ++", "Item 14: Declare functions noexcept if they don't emit exceptions" pour une lecture plus approfondie.


Cela aurait quand même beaucoup plus de sens si des exceptions étaient implémentées en C ++ comme en Java, où vous marquez une méthode qui peut être lancée avec un throwsmot clé au lieu de noexceptnégative. Je ne peux tout simplement pas obtenir certains choix de conception C ++ ...
doc

Ils l'ont nommé noexceptparce que throwc'était déjà pris. Autrement dit, ils throwpeuvent être utilisés presque comme vous le mentionnez, sauf qu'ils ont bâclé la conception de celui-ci, il est donc devenu presque inutile - même nuisible. Mais nous sommes coincés avec cela maintenant car le supprimer serait un changement de rupture avec peu d'avantages. Il en noexceptva de même pour l'essentiel throw_v2.
AnorZaken

Comment n'est throwpas utile?
curiousguy

@curiousguy "throw" lui-même (pour lancer des exceptions) est utile, mais "throw" en tant que spécificateur d'exception a été déconseillé et même en C ++ 17 supprimé. Pour les raisons pour lesquelles les spécificateurs d'exceptions ne sont pas utiles, consultez cette question: stackoverflow.com/questions/88573/…
Philipp Claßen

1
@ PhilippClaßen Le throw()spécificateur d'exception n'a pas fourni la même garantie que nothrow?
curiousguy

17

Il existe de nombreux exemples de fonctions que je sais ne lanceront jamais, mais pour lesquelles le compilateur ne peut pas le déterminer par lui-même. Dois-je ajouter noexcept à la déclaration de fonction dans tous ces cas?

Quand vous dites "je sais [qu'ils] ne lanceront jamais", vous voulez dire en examinant l'implémentation de la fonction que vous savez que la fonction ne lancera pas. Je pense que cette approche est à l'envers.

Il vaut mieux se demander si une fonction peut lever des exceptions pour faire partie de la conception de la fonction: aussi importante que la liste d'arguments et si une méthode est un mutateur (... const). Déclarer que "cette fonction ne lève jamais d'exceptions" est une contrainte sur l'implémentation. L'omettre ne signifie pas que la fonction peut lever des exceptions; cela signifie que la version actuelle de la fonction et toutes les versions futures peuvent lever des exceptions. C'est une contrainte qui rend la mise en œuvre plus difficile. Mais certaines méthodes doivent avoir la contrainte d'être pratiquement utiles; plus important encore, ils peuvent donc être appelés à partir de destructeurs, mais aussi pour la mise en œuvre de code de «roll-back» dans des méthodes qui offrent une forte garantie d'exception.


C'est de loin la meilleure réponse. Vous faites une garantie aux utilisateurs de votre méthode, ce qui est une autre façon de dire que vous limitez votre implémentation pour toujours (sans rupture-changement). Merci pour la perspective éclairante.
AnorZaken

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.