49 votes

Pourquoi les opérateurs d'accès au vecteur ne sont-ils pas spécifiés comme noexcept ?

Pourquoi std::vector 's operator[] , front y back ne sont pas spécifiées en tant que noexcept ?

73voto

Sebastian Redl Points 18816

La politique de la norme en matière de noexcept est de ne marquer que les fonctions qui ne peut pas o ne doit pas échouent, mais pas ceux qui sont simplement spécifiés pour ne pas lancer d'exceptions. En d'autres termes, toutes les fonctions qui ont un domaine limité (passez les mauvais arguments et vous obtiendrez un comportement indéfini) ne sont pas noexcept même s'il n'est pas spécifié qu'ils doivent être lancés.

Les fonctions qui sont marquées sont des fonctions telles que swap (ne doit pas échouer, car la sécurité des exceptions repose souvent sur ce point) et numeric_limits::min (ne peut pas échouer, renvoie une constante d'un type primitif).

La raison en est que les implémenteurs peuvent vouloir fournir des versions de débogage spéciales de leurs bibliothèques qui déclenchent diverses situations de comportement non défini, de sorte que les cadres de test puissent facilement détecter l'erreur. Par exemple, si vous utilisez un index hors limite avec l'option vector::operator[] ou appelez le front o back sur un vecteur vide. Certaines implémentations veulent lancer une exception à cet endroit (ce qu'elles sont autorisées à faire : puisqu'il s'agit d'un comportement non défini, elles peuvent faire n'importe quoi), mais une norme de noexcept sur ces fonctions rend cela impossible.

2 votes

Un comportement hors limites est un comportement non défini, et dans le cas d'un comportement non défini, tout peut arriver. Le comportement non défini n'est pas limité par noexcept. Cependant, lorsqu'une exception se produit, être noexcept se terminera certainement, ce qui n'est probablement pas souhaité dans les cadres de test.

8 votes

@PlasmaHH En théorie, vous avez raison. Une fois qu'un comportement indéfini est invoqué, une fonction qui est noexcept pourrait vraiment se détendre. Cependant, les considérations pratiques de la génération de code et le fait que de tels modes de débogage transforment un comportement non défini en un comportement défini (en lançant une exception) font que cette approche n'est pas viable pour l'écriture d'une implémentation C++. Si une fonction est noexcept L'appelant pourrait bien manquer de tables de déroulement, de sorte que la tentative de déroulement entraînerait un plantage ou enverrait l'exécution dans un pays sans fin, ce qui va à l'encontre de l'intérêt de lancer une exception.

0 votes

Donc, selon cette logique, la fonction noexcept peut recevoir n'importe quel argument ?

16voto

Xin Huang Points 705

En complément de la réponse de @SebastianRedl : pourquoi vous aurez besoin de noexcept ?

noexcept et std::vector

Comme vous le savez peut-être, un vector a sa capacité. S'il est plein lorsque push_back Il allouera une plus grande mémoire, copiera (ou déplacera depuis C++11) tous les éléments existants vers le nouveau tronc, puis ajoutera le nouvel élément à l'arrière.

Use copy constructor to expand a vector

Mais que se passe-t-il si une exception est levée lors de l'allocation de la mémoire ou de la copie de l'élément dans le nouveau tronc ?

  • Si une exception est levée pendant l'allocation de la mémoire, le vecteur est dans son état d'origine. Il suffit de relancer l'exception et de laisser l'utilisateur s'en occuper.

  • Si une exception est levée pendant la copie d'éléments existants, tous les éléments copiés seront détruits en appelant le destructeur, le coffre alloué sera libéré et l'exception levée sera gérée par le code de l'utilisateur. (1)
    Après avoir tout détruit, le vecteur revient à son état d'origine. Il est maintenant possible de lancer une exception pour permettre à l'utilisateur de la gérer, sans fuite de ressources.

noexcept et move

À l'ère de C++ 11, nous disposons d'une arme puissante appelée move . Il nous permet de voler les ressources des objets inutilisés. std::vector utilisera move lorsqu'il doit augmenter (ou diminuer) la capacité, tant que les move L'opération est pas d'exception .

Supposons qu'une exception se produise pendant le déplacement, le tronc précédent n'est pas le même qu'avant. move se produit : les ressources sont volées, ce qui laisse le vecteur dans une situation d'urgence. cassé État. L'utilisateur ne peut pas gérer l'exception car tout est dans un état non déterministe.

Use move constructor to expand a vector

C'est pourquoi std::vector s'appuie sur move constructor être pas d'exception .

Il s'agit d'une démonstration de la manière dont le code client s'appuierait sur les éléments suivants noexcept en tant que spécification d'interface. Si, par la suite, le noexcept n'est pas respectée, tout code qui en dépend précédemment sera cassé.


Pourquoi ne pas simplement marquer toutes les fonctions comme noexcept ?

Réponse courte : il est difficile d'écrire du code sans risque d'exception.

Réponse longue : noexcept fixer une limite stricte aux développeurs qui mettent en œuvre l'interface. Si vous souhaitez supprimer l'interface noexcept à partir d'une interface, le code client peut être cassé comme dans l'exemple du vecteur donné ci-dessus ; mais si vous voulez rendre une interface noexcept Vous êtes libre de le faire à tout moment.

Ainsi, ce n'est qu'en cas de nécessité que l'on peut marquer une interface comme noexcept .


Dans le cadre de la Going Native 2013 (en anglais) Scott Meyers a parlé de la situation décrite ci-dessus, à savoir que sans l'aide de l'Union européenne, il n'est pas possible de faire des économies. noexcept la cohérence d'un programme est mise à mal.

J'ai également écrit un blog à ce sujet : https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html

5voto

FrankHB Points 21

En bref, il existe des fonctions spécifiées avec ou sans noexcept . C'est voulu, car ils sont différents. Le principe est le suivant : une fonction dont le comportement est indéfini (par exemple, en raison d'arguments inappropriés) ne doit pas être associée à la fonction noexcept .

Ce document a explicitement spécifié que ces membres n'avaient pas noexcept . Certains membres de la vector ont été utilisés comme exemples :

Voici quelques exemples de fonctions faisant l'objet de contrats étendus vector<T>::begin() y vector<T>::at(size_type) . Voici quelques exemples de fonctions qui ne font pas l'objet d'un contrat étendu vector<T>::front() y vector<T>::operator[](size_type) .

Véase ce document pour la motivation initiale et la discussion détaillée. Le problème réaliste le plus évident est celui de la testabilité.

0 votes

Définition de wide contract du même journal : Un contrat large pour une fonction ou une opération ne spécifie aucun comportement non défini. Un tel contrat n'a pas de conditions préalables : Une fonction avec un contrat large n'impose aucune contrainte d'exécution supplémentaire sur ses arguments, sur l'état d'un objet ou sur un état global externe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X