web-dev-qa-db-fra.com

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

J'ai eu une belle conversation avec quelqu'un sur les chutes de std::stoi. Pour le dire franchement, il utilise std::strtol En 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", Empêchant stoi de lancer std::invalid_argument.

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

Les deux affichent un succès sur "123" Et un é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 s'appuyer sur strtol. Qu'en est-il maintenant de strtol? J'ai trouvé cela dans le projet C11:

§7.22.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.

Étant donné la situation de la transmission de "abc", La norme 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 doit être retourné, comme indiqué par ceci:

§7.22.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 lancer std::invalid_argument.


Le résultat de cela est important pour moi parce que je ne veux pas recommander stoi comme une meilleure alternative aux autres méthodes de conversion de chaîne en int, ou l'utiliser moi-même comme si cela fonctionnait comme vous l'attendiez , s'il ne capture pas le texte comme une conversion non valide.

Donc, après tout cela, je me suis trompé quelque part? Il me semble que j'ai une bonne preuve de la levée de cette exception. Ma preuve est-elle valide, ou std::stoi N'est-il pas garanti de lever cette exception lorsqu'elle reçoit "abc"?

58
chris

std::stoi Génère-t-il une erreur sur l'entrée "abcxyz"?

Oui.

Je pense que votre confusion peut venir du fait que strtol ne rapporte jamais ne erreur sauf en cas de débordement. Il peut signaler qu'aucune conversion n'a été effectuée, mais cela n'est jamais considéré comme une condition d'erreur dans la norme C.

strtol est défini de manière similaire par les trois normes C, et je vous épargnerai les détails ennuyeux, mais il définit essentiellement 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 simple: c'est un nombre)
  • la séquence du sujet n'est pas vide
  • une conversion s'est produite
  • *endptr != nptr (Cela n'a de sens que lorsque endptr n'est pas nul)

En cas de débordement, la conversion est toujours supposée avoir eu lieu.

Maintenant, il est tout à fait clair que parce que "abcxyz" Ne contient pas de nombre, la séquence objet de la chaîne "abcxyz" Doit être vide, afin qu'aucune conversion ne puisse être effectuée. Le programme C90/C99/C11 suivant le confirmera expérimentalement:

#include <stdio.h>
#include <stdlib.h>

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::stoidoit lancer invalid_argument Lorsqu'elle reçoit l'entrée "abcxyz" Sans argument de base facultatif.


Cela signifie-t-il que std::stoi A une vérification d'erreur satisfaisante?

Non. La personne à qui vous avez parlé a raison lorsqu'elle dit que std::stoi Est plus indulgent que d'effectuer la vérification complète errno == 0 && end != start && *end=='\0' Après std::strtol, Parce que std::stoi En silence supprime tous les caractères à partir du premier caractère non numérique de la chaîne.

En fait, du haut de ma tête, la seule langue dont la conversion native se comporte un peu comme std::stoi Est Javascript, et même dans ce cas, vous devez forcer la base 10 avec parseInt(n, 10) pour éviter le cas particulier de l'hexadécimal Nombres:

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 connais aucune fonction intégrée qui fasse cela, mais boost::lexical_cast<int> Fera ce que vous voulez. Il est particulièrement strict car il rejette même les espaces environnants, contrairement à la fonction int() de Python. Notez que les caractères non valides et les débordements entraînent la même exception, boost::bad_lexical_cast.

#include <boost/lexical_cast.hpp>

int main() {
    std::string s = "42";
    try {
        int n = boost::lexical_cast<int>(s);
        std::cout << "n = " << n << std::endl;
    } catch (boost::bad_lexical_cast) {
        std::cout << "conversion failed" << std::endl;
    }
}
72
Generic Human