std :: stoi实际上是否可以安全使用?

chr*_*ris 58 c++ standards language-lawyer

我与某人谈论了垮台事件std::stoi.坦率地说,它在std::strtol内部使用,如果报告错误则抛出.但是,根据他们的说法,std::strtol不应该为输入报告错误"abcxyz",导致stoi不抛出std::invalid_argument.

首先,这里有两个在GCC上测试过的关于这些案例行为的程序:
strtol
stoi

他们都表现出成功"123"和失败"abc".


我查看标准以获取更多信息:

§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.
Run Code Online (Sandbox Code Playgroud)

总结了依赖的行为strtol.那怎么样strtol?我在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.
Run Code Online (Sandbox Code Playgroud)

鉴于传入的情况"abc",C标准规定nptr,指向字符串的开头,将存储endptr,指针传入.这似乎与测试一致.此外,应返回0,如下所述:

§7.22.1.4

If no conversion could be performed, zero is returned.
Run Code Online (Sandbox Code Playgroud)

之前的参考文献表示不会执行任何转换,因此它必须返回0.这些条件现在符合C++ 11 stoi投掷标准std::invalid_argument.


这对我很重要,因为我不想推荐stoi作为其他字符串转换为int的更好的替代方法,或者自己使用它就好像它按照你期望的方式工作,如果它没有将文本捕获为无效转换.

所以这一切之后,我在某个地方出错了吗?在我看来,我有很好的证据证明这个例外被抛出.我的证明是否有效,或者std::stoi不能保证在给出时抛出该异常"abc"

Gen*_*man 73

是否std::stoi在输入上抛出错误"abcxyz"

是.

我认为你的困惑可能来自于除了溢出之外strtol从不报告错误的事实.它可以报告没有执行转换,但这在C标准中从未被称为错误条件.

strtol所有三个C标准都是类似地定义的,我将为您提供无聊的细节,但它基本上定义了一个"主题序列",它是与实际数字对应的输入字符串的子串.以下四个条件是等效的:

  • 主题序列具有预期的形式(用简单的英语:它是一个数字)
  • 主题序列是非空的
  • 发生了转换
  • *endptr != nptr(这只在endptr非空时才有意义)

当存在溢出时,仍然认为转换已经发生.

现在,很明显因为"abcxyz"不包含数字,所以字符串的主题序列"abcxyz"必须为空,这样就不能进行转换.以下C90/C99/C11程序将通过实验确认:

#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;
}
Run Code Online (Sandbox Code Playgroud)

这意味着当给定输入而没有可选的基本参数时,std::stoi 必须抛出任何一致的实现.invalid_argument"abcxyz"


这是否意味着std::stoi有令人满意的错误检查?

当号她说,你和她说话的人是正确的std::stoi比进行全面检查较为宽松errno == 0 && end != start && *end=='\0'std::strtol,因为std::stoi默默除掉从字符串中的第一个非数字字符开始的所有字符.

事实上,在我的脑海中,唯一的语言,其原生转换行为有点像std::stoiJavascript,即使这样,你必须强制基数10 parseInt(n, 10)以避免十六进制数字的特殊情况:

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...  
Run Code Online (Sandbox Code Playgroud)

注意:在处理空格和冗余+符号时,语言之间也存在差异.


好的,所以我想要完整的错误检查,我应该使用什么?

我不知道有任何内置功能可以做到这一点,但boost::lexical_cast<int>会做你想要的.它特别严格,因为它甚至拒绝周围的空白,与Python的int()功能不同.请注意,无效字符和溢出会导致相同的异常,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @chris:欢迎来到C/C++标准库的精彩世界:*总有*一小块难题缺失:-) (4认同)
  • 你是不是忘记了 std::stoi 有一个可选的第二个参数,函数通过它报告输入字符串中无法转换的第一个字符的索引?IMO 这意味着该函数确实执行了完整的错误检查。 (4认同)