web-dev-qa-db-ja.com

std :: stoiは実際に使用しても安全ですか?

std::stoiの没落について誰かと素敵な会話をしました。率直に言って、std::strtolを内部的に使用し、エラーが報告された場合はスローします。しかし、彼らによれば、std::strtol"abcxyz"の入力に対してエラーを報告してはならず、stoistd::invalid_argumentをスローしません。

まず、これらのケースの動作についてGCCでテストされた2つのプログラムを次に示します。
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を返す必要があると述べていました。これらの条件は、stoi throwing std::invalid_argumentのC++ 11標準に準拠しています。


この結果は私にとって重要です。なぜなら、他の文字列からintへの変換方法の代わりとしてstoiを推奨したり、期待どおりに機能するかのように自分で使用したりしたくないからです。 、テキストが無効な変換としてキャッチされない場合。

それで、このすべての後、私はどこか間違ったことをしましたか?私には、この例外がスローされることの良い証拠があるように思えます。私の証明は有効ですか、またはstd::stoiは、"abc"が与えられたときにその例外をスローすることが保証されていませんか?

58
chris

_std::stoi_は、入力_"abcxyz"_でエラーをスローしますか?

はい。

あなたの混乱は、strtolがオーバーフローを除いてエラーを決して報告しないという事実から来ると思います。変換が実行されなかったことを報告できますが、これはC標準ではエラー状態と呼ばれることはありません。

strtolは3つのC標準すべてで同様に定義されており、退屈な詳細は省きますが、基本的には、実際の番号に対応する入力文字列のサブストリングである「サブジェクトシーケンス」を定義します。次の4つの条件は同等です。

  • サブジェクトシーケンスは期待される形式です(わかりやすい英語:数値です)
  • サブジェクトシーケンスが空ではない
  • 変換が発生しました
  • _*endptr != nptr_(これはendptrがnull以外の場合にのみ意味があります)

オーバーフローがある場合、変換はまだ発生したと言われます。

ここで、_"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_ must throw _"abcxyz"_の準拠実装を意味します。


これは_std::stoi_に十分なエラーチェックがあることを意味しますか?

いいえ。_std::stoi_はすべての文字を静かに削除するため、_errno == 0 && end != start && *end=='\0'_は_std::strtol_の後に完全なチェック_std::stoi_を実行するよりも寛大であると言ったときにあなたが話していた人は正しいです。文字列の最初の非数値文字から始まります。

実際、ネイティブ変換が_std::stoi_のように動作する唯一の言語はJavascriptであり、その場合でもparseInt(n, 10)を使用してベース10を強制し、16進数の特殊なケースを回避する必要があります:

_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;
    }
}
_
72
Generic Human