69

我与某人就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.

这总结了依赖的行为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.

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

§7.22.1.4

If no conversion could be performed, zero is returned.

之前的参考资料说不会执行任何转换,所以它必须返回 0。这些条件现在符合 C++11 标准的stoithrowing std::invalid_argument


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

那么在这一切之后,我是不是哪里出错了?在我看来,我有很好的证据证明这个异常被抛出。我的证明是否有效,或者std::stoi不能保证在给出时抛出该异常"abc"

4

1 回答 1

85

是否std::stoi在输入上引发错误"abcxyz"

是的。

我认为您的困惑可能来自于除了溢出之外strtol从不报告错误的事实。它可以报告未执行任何转换,但这在 C 标准中从未被称为错误情况。

strtol所有三个 C 标准都对它进行了类似的定义,我不再赘述无聊的细节,但它基本上定义了一个“主题序列”,它是与实际数字相对应的输入字符串的子字符串。以下四个条件是等价的:

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

当发生溢出时,仍然可以说转换已经发生。

现在已经很清楚了,因为"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;
}

这意味着当给定没有可选基本参数的输入时,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...  

注意:在处理空格和多余的 + 符号方面,语言之间也存在差异。


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

我不知道有任何内置函数可以做到这一点,但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;
    }
}
于 2012-07-22T11:16:50.440 回答