Std :: stoi est-il réellement sûr à utiliser?

J’ai eu une belle conversation avec quelqu’un au sujet des inconvénients de std::stoi . Pour le dire franchement, il utilise std::strtol interne et lance si cela signale une erreur. Selon eux, cependant, std::strtol ne devrait pas signaler une erreur pour une entrée de "abcxyz" , stoi de lancer std::invalid_argument .

Tout d’abord, voici deux programmes testés sur GCC concernant les comportements de ces cas:
strtol
stoi

Les deux montrent le succès sur "123" et l’échec sur "abc" .


J’ai regardé dans la norme pour tirer plus d’informations:

§ 21.5

 Throws: invalid_argument if strtol, strtoul, strtoll, or strtoull reports that no conversion could be performed. Throws out_of_range if the converted value is outside the range of representable values for the return type. 

Cela résume le comportement de compter sur strtol . Maintenant, qu’en est-il de strtol ? Je l’ai trouvé dans le projet C11:

§7.2.2.1.4

 If the subject sequence is empty or does not have the expected form, no conversion is performed; the value of nptr is stored in the object pointed to by endptr, provided that endptr is not a null pointer. 

Compte tenu de la situation de passage à "abc" , le standard C stipule que nptr , qui pointe vers le début de la chaîne, serait stocké dans endptr , le pointeur transmis. Cela semble cohérent avec le test. De plus, 0 devrait être retourné, comme indiqué par ceci:

§7.2.2.1.4

 If no conversion could be performed, zero is returned. 

La référence précédente indiquait qu’aucune conversion ne serait effectuée, elle doit donc retourner 0. Ces conditions sont désormais conformes à la norme C ++ 11 pour stoi std::invalid_argument .


Le résultat de ceci est important pour moi parce que je ne veux pas contourner stoi comme une meilleure alternative à d’autres méthodes de chaîne pour convertir un int, ou l’utiliser moi-même comme si vous le souhaitiez, si ça ne marche pas. t attraper le texte en tant que conversion non valide.

Donc après tout ça, je me suis trompé quelque part? Il me semble avoir une bonne preuve de cette exception. Est-ce que ma preuve est valide ou est-ce que std::stoi n’est pas garanti pour lancer cette exception quand on lui donne "abc" ?

Est-ce que std::stoi jette une erreur sur l’entrée "abcxyz" ?

Oui.

Je pense que votre confusion peut provenir du fait que strtol ne signale jamais une erreur, sauf en cas de dépassement de strtol . Il peut signaler qu’aucune conversion n’a été effectuée, mais il ne s’agit jamais d’une condition d’erreur dans la norme C.

strtol est défini de la même manière par les trois standards C, et je vous épargnerai les détails ennuyeux, mais il définit fondamentalement une “séquence de sujet” qui est une sous-chaîne de la chaîne d’entrée correspondant au nombre réel. Les quatre conditions suivantes sont équivalentes:

  • la séquence du sujet a la forme attendue (en anglais clair: c’est un nombre)
  • la séquence du sujet est non vide
  • une conversion s’est produite
  • *endptr != nptr (cela n’a de sens que si endptr est non nul)

Quand il y a un débordement, la conversion est toujours dite.

Or, il est clair que "abcxyz" ne contient pas de nombre, la séquence de sujet de la chaîne "abcxyz" doit être vide, de sorte qu’aucune conversion ne puisse être effectuée. Le programme C90 / C99 / C11 suivant le confirmera expérimentalement:

 #include  #include  int main() { char *nptr = "abcxyz", *endptr[1]; strtol(nptr, endptr, 0); if (*endptr == nptr) printf("No conversion could be performed.\n"); return 0; } 

Cela implique que toute implémentation conforme de std::stoi doit invalid_argument un argument invalid_argument lorsqu’elle reçoit l’entrée "abcxyz" sans argument de base facultatif.


Est-ce que cela signifie que std::stoi a une vérification d’erreur satisfaisante?

Non. La personne à qui vous avez parlé est correcte quand elle dit que std::stoi est plus indulgente que de faire la vérification complète errno == 0 && end != start && *end=='\0' après std::strtol car std::stoi silencieusement tous les caractères à partir du premier caractère non numérique de la chaîne.

En fait, le seul langage dont la conversion native se comporte un peu comme std::stoi est le Javascript, et même alors, vous devez forcer la base 10 avec parseInt(n, 10) pour éviter le cas spécial des nombres hexadécimaux:

 input | std::atoi std::stoi Javascript full check ===========+============================================================= hello | 0 error error(NaN) error 0xygen | 0 0 error(NaN) error 0x42 | 0 0 66 error 42x0 | 42 42 42 error 42 | 42 42 42 42 -----------+------------------------------------------------------------- languages | Perl, Ruby, Javascript Javascript C#, Java, | PHP, C... (base 10) Python... 

Remarque: il existe également des différences entre les langues dans la gestion des espaces et des signes redondants.


Ok, donc je veux une vérification complète des erreurs, que dois-je utiliser?

Je ne suis au courant d’aucune fonction intégrée qui fait cela, mais boost::lexical_cast fera ce que vous voulez. Il est particulièrement ssortingct puisqu’il rejette même les espaces blancs, contrairement à la fonction int() de Python. Notez que les caractères non valides et les dépassements entraînent la même exception, boost::bad_lexical_cast .

 #include  int main() { std::ssortingng s = "42"; try { int n = boost::lexical_cast(s); std::cout << "n = " << n << std::endl; } catch (boost::bad_lexical_cast) { std::cout << "conversion failed" << std::endl; } }