[英]stringstream unsigned input validation
我正在編寫程序的一部分,該程序分析並驗證程序控制台參數中的某些用戶輸入。 我選擇將stringstream用於此目的,但是在讀取無符號類型時遇到問題。
下一個模板旨在從給定的字符串中讀取請求的類型:
#include <iostream>
#include <sstream>
#include <string>
using std::string;
using std::stringstream;
using std::cout;
using std::endl;
template<typename ValueType>
ValueType read_value(string s)
{
stringstream ss(s);
ValueType res;
ss >> res;
if (ss.fail() or not ss.eof())
throw string("Bad argument: ") + s;
return res;
}
// +template specializations for strings, etc.
int main(void)
{
cout << read_value<unsigned int>("-10") << endl;
}
如果類型是無符號的並且輸入字符串包含負數,我希望看到異常拋出(由ss.fail() = true
引起)。 但是stringstream產生強制轉換為無符號類型值(書面樣本中為4294967286)。
如何固定該示例以實現所需的行為(最好是不回退到c函數)? 我知道可以通過簡單的第一個符號檢查來完成,但是我可以放置前導空格。 我可以編寫自己的解析器,但不要相信這個問題是如此不可預測,並且標准庫無法解決它。
隱藏在字符串流運算符深處的無符號類型函數是strtoull和strtoul。 它們以描述的方式工作,但是提到的功能是低級的 。 為什么stringstream不提供某些驗證級別? (我只是希望我做錯了,但是確實需要做一些動作才能實現)。
版本免責聲明:對於C ++ 03,答案是不同的。 以下處理C ++ 11。
首先,讓我們分析發生了什么。
ss >> res;
這將調用std::istream::operator>>(unsigned)
。 在[istream.formatted.arithmetic] / 1中,效果定義如下:
這些提取器充當格式化的輸入函數(如27.7.2.2.1中所述)。 構造哨兵對象后,將發生轉換,就像由以下代碼片段執行的那樣:
typedef num_get< charT,istreambuf_iterator<charT,traits> > numget; iostate err = iostate::goodbit; use_facet< numget >(loc).get(*this, 0, *this, err, val); setstate(err);
在上面的片段中,
loc
代表basic_ios
類的私有成員。
在[istream :: sentry]的格式化輸入函數之后, sentry
對象的主要作用是消耗前導空白字符。 如果發生錯誤(流處於失敗/ eof狀態),這也將阻止執行上面顯示的代碼。
使用的語言環境是"C"
語言環境。 理由:
對於該stringstream
通過構成stringstream ss(s);
,該iostream的語言環境是構建時的當前全局語言環境(保證在[ios.base.locales] / 4的兔子洞的深處)。 由於未在OP的程序中更改全局語言環境,因此[locale.cons] / 2指定“經典”語言環境,即"C"
語言環境。
use_facet< numget >(loc).get
使用成員函數num_get<char>::get(iter_type in, iter_type end, ios_base&, ios_base::iostate& err, unsigned int& v) const;
在[locale.num.get]中指定(請注意unsigned int
,一切都還可以)。 [facet.num.get.virtuals]中描述了“ C”語言環境的字符串-> unsigned int
轉換的詳細信息。 一些有趣的細節:
strtoull
。 ios_base::failbit
分配給err
。 特別是:“要存儲的數值可以是以下其中之一:[...]最負的可表示值,或者對於無符號整數類型為零,如果該字段表示的值太大而無法在值中表示ios_base::failbit
分配給err
。” 我們需要轉到strtoull
,以了解第5段中strtoull
的定義:
如果主題序列以減號開頭,則轉換結果取反(返回類型)。
並根據第8段:
如果正確的值超出可表示的值的范圍,則返回
LONG_MIN
,LONG_MAX
,LLONG_MIN
,LLONG_MAX
,ULONG_MAX
或ULLONG_MAX
(根據返回值和值的符號,如果有的話),並且宏ERANGE
值為存儲在errno
看來,它已經在過去的辯論,如果負值被認為是有效的輸入strotoul
。 無論如何,問題在於此功能。 快速檢查gcc表示它被視為有效輸入,因此您觀察到的行為也是如此。
歷史注釋:C ++ 03
C ++ 03在num_get
轉換中使用了scanf
。 不幸的是,我還不確定(如何)指定scanf
的轉換以及在什么情況下會發生錯誤。
顯式錯誤檢查:
我們可以通過使用帶符號的值進行轉換並測試<0
來手動插入該檢查,或者尋找-
字符(由於可能存在的本地化問題,這不是一個好主意)。
一個num_get
構面,用於支持顯式檢查簽名。 對於無符號類型,拒絕以'-'
(在空格之后)開頭的任何非零數字,並使用默認的C語言環境的num_get
進行實際轉換。
#include <locale>
#include <istream>
#include <ios>
#include <algorithm>
template <class charT, class InputIterator = std::istreambuf_iterator<charT> >
class num_get_strictsignedness : public std::num_get <charT, InputIterator>
{
public:
typedef charT char_type;
typedef InputIterator iter_type;
explicit num_get_strictsignedness(std::size_t refs = 0)
: std::num_get<charT, InputIterator>(refs)
{}
~num_get_strictsignedness()
{}
private:
#define DEFINE_DO_GET(TYPE) \
virtual iter_type do_get(iter_type in, iter_type end, \
std::ios_base& str, std::ios_base::iostate& err, \
TYPE& val) const override \
{ return do_get_templ(in, end, str, err, val); } // MACRO END
DEFINE_DO_GET(unsigned short)
DEFINE_DO_GET(unsigned int)
DEFINE_DO_GET(unsigned long)
DEFINE_DO_GET(unsigned long long)
// not sure if a static locale::id is required..
template <class T>
iter_type do_get_templ(iter_type in, iter_type end, std::ios_base& str,
std::ios_base::iostate& err, T& val) const
{
using namespace std;
if(in == end)
{
err |= ios_base::eofbit;
return in;
}
// leading white spaces have already been discarded by the
// formatted input function (via sentry's constructor)
// (assuming that) the sign, if present, has to be the first character
// for the formatting required by the locale used for conversion
// use the "C" locale; could use any locale, e.g. as a data member
// note: the signedness check isn't actually required
// (because we only overload the unsigned versions)
bool do_check = false;
if(std::is_unsigned<T>{} && *in == '-')
{
++in; // not required
do_check = true;
}
in = use_facet< num_get<charT, InputIterator> >(locale::classic())
.get(in, end, str, err, val);
if(do_check && 0 != val)
{
err |= ios_base::failbit;
val = 0;
}
return in;
}
};
用法示例:
#include <sstream>
#include <iostream>
int main()
{
std::locale loc( std::locale::classic(),
new num_get_strictsignedness<char>() );
std::stringstream ss("-10");
ss.imbue(loc);
unsigned int ui = 42;
ss >> ui;
std::cout << "ui = "<<ui << std::endl;
if(ss)
{
std::cout << "extraction succeeded" << std::endl;
}else
{
std::cout << "extraction failed" << std::endl;
}
}
筆記:
1
char
, wchar_t
, charXY_t
),都需要添加自己的構面(可以是num_get_strictsignedness
模板的不同實例化) "-0"
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.