一個(gè)關(guān)于使用boost::lexical_cast產(chǎn)生異常的問題,關(guān)鍵代碼如下
string str(8,'\0');
strncpy(&str.at(0),"1234567",7);
cout << lexical_cast(str) << endl;
結(jié)果運(yùn)行的時(shí)候發(fā)生如下異常
terminate called after throwing an instance of 'boost::bad_lexical_cast'
what(): bad lexical cast: source type value could not be interpreted as target
我們知道boost::lexical_cast最終使用的stringstream實(shí)現(xiàn)的數(shù)值類型轉(zhuǎn)換,所以,我們使用如下例子,做測試
stringstream ss;
ss << str;
ss >> result;
cout << "new Result: " << result << endl;
編譯運(yùn)行后,輸出
new Result: 1234567
可以正常顯示,好像沒有問題,
我們察看一下boost的源代碼
vim /usr/include/boost/lexical_cast.hpp
察看lexical_cast函數(shù)
template
Target lexical_cast(Source arg)
{
detail::lexical_stream interpreter;
Target result;
if(!(interpreter << arg && interpreter >> result))
throw_exception(bad_lexical_cast(typeid(Target), typeid(Source)));
return result;
}
可見lexical_cast函數(shù)非常簡單,就是具體執(zhí)行operator<<和operator>>兩個(gè)操作,只要這兩個(gè)操作有一個(gè)失敗就拋出一個(gè)異常,為了確認(rèn)是那步出的錯(cuò),我們在程序中手工執(zhí)行這兩個(gè)操作。代碼如下
detail::lexical_stream interpreter;
int result;
if(!(interpreter << str ))
{
cout << "Error 1" << endl;
}
if(!(interpreter >> result))
{
cout << "Error 2" << endl;
}
cout << result << endl;
編譯運(yùn)行后輸出
Error 2
從這里我們知道,lexical_cast是在執(zhí)行輸出流的時(shí)候發(fā)生的問題,察看detail的operator>>函數(shù),考試.大提示其源代碼如下
template
bool operator>>(InputStreamable &output)
{
return !is_pointer::value &&
stream >> output &&
(stream >> std::ws).eof();
}
根據(jù)以上代碼和我們使用stringstream做的測試,基本上可以確定在stream>>output(包括次步)都是正確的,可能出現(xiàn)問題的是(stream >> std::ws).eof();
這里解釋下std::ws和stringstring::eof()函數(shù)
Std::ws函數(shù)聲明在
/usr/include/c++/3.4.4/bits/istream.tcc
源代碼如下
// 27.6.1.4 Standard basic_istream manipulators
template
basic_istream<_CharT,_Traits>&
ws(basic_istream<_CharT,_Traits>& __in)
{
typedef basic_istream<_CharT, _Traits> __istream_type;
typedef typename __istream_type::__streambuf_type __streambuf_type;
typedef typename __istream_type::__ctype_type __ctype_type;
typedef typename __istream_type::int_type __int_type;
const __ctype_type& __ct = use_facet<__ctype_type>(__in.getloc());
const __int_type __eof = _Traits::eof();
__streambuf_type* __sb = __in.rdbuf();
__int_type __c = __sb->sgetc();
while (!_Traits::eq_int_type(__c, __eof)
&& __ct.is(ctype_base::space, _Traits::to_char_type(__c)))
__c = __sb->snextc();
if (_Traits::eq_int_type(__c, __eof))
__in.setstate(ios_base::eofbit);
return __in;
}
主要作用是過濾輸入流中的空格,\n\r等字符。stream >> std::ws目的就是把輸入流中轉(zhuǎn)換完整形后的剩余流內(nèi)容(假如有的話)寫入std::ws,當(dāng)然只能寫入其中的空格和\n\r等字符。
stringstring::eof()函數(shù)參考 http://www.cppreference.com/wiki/io/eof 部分
該函數(shù)的主要作用是,如果到達(dá)流的結(jié)束位置返回true,否則返回false
根據(jù)以上信息,我們編寫測試用例
stringstream ss;
ss << str;
ss >> result;
cout << "new Result: " << result << endl;
cout << ss.eof() << endl;
cout << (ss >> std::ws).eof() << endl; 編譯運(yùn)行后輸出
new Result: 1234567
0
0
由此可見,雖然我們使用ss時(shí),可以輸出想要的正確結(jié)果,但是我們?nèi)鄙僮詈蟮陌踩?yàn)證,而boost::lexical_cast就做了這方面的驗(yàn)證。
其實(shí)例子中的’\0’在開始的時(shí)候,起了不小的誤導(dǎo)作用,開始以為是boost::lexical_cast無法處理最后末尾是’\0’的字符串,到現(xiàn)在其實(shí)不然,我們把’\0’轉(zhuǎn)換為’a’字符一樣會(huì)出現(xiàn)這種問題,但是我們使用’\n’,’\r’和空格等字符就不會(huì)出現(xiàn)這種問題,現(xiàn)在我們知道其根源就是在字符轉(zhuǎn)換過程中輸入流沒有輸入全部字符,所以流的結(jié)束標(biāo)志EOF,一直為0。
其實(shí)在上面的應(yīng)用中我們不能一直認(rèn)為boost::lexical_cast的方法一定是好的。在我們編成過程中,常見的轉(zhuǎn)換是把一段字符串中含有數(shù)字和字母的字符串中的數(shù)字串轉(zhuǎn)換為整形,這樣的如果我們使用boost::lexical_cast的話,永遠(yuǎn)得不到正確結(jié)果了,每次都會(huì)有異常拋出,這時(shí)候我們可以使用stringstream,轉(zhuǎn)換后不判斷eof(),這樣就可以得到我們想要的整數(shù)。
在上面的測試中,突然想到一個(gè)變態(tài)的想法,STL中的字符串轉(zhuǎn)為整形的流實(shí)現(xiàn)是怎么做的,不過SGI的STL真夠難堪的。
大體查找過程如下
(1): Vim /usr/include/c++/3.4.4/sstream
發(fā)現(xiàn)引用了istream
(2): Vim /usr/include/c++/3.4.4/ istream
發(fā)現(xiàn)operator<<(int)的實(shí)現(xiàn)在bits/istream.tcc文件中
(3): Vim /usr/include/c++/3.4.4/ bits/istream.tcc
發(fā)現(xiàn)const __num_get_type& __ng = __check_facet(this->_M_num_get);__ng.get(*this, 0, *this, __err, __l);所以查找__num_get_type類型中的get函數(shù),同時(shí)發(fā)現(xiàn)istream.tcc中的#include 比較陌生,同時(shí)在istream中查找__num_get_type 類型為typedef num_get<_CharT, istreambuf_iterator<_CharT, _Traits> > __num_get_type; 所以,最終要查找的類型為num_get
(4): Vim /usr/include/c++/3.4.4/locale
發(fā)現(xiàn)這個(gè)文件中包括以下頭文件
#include
#include
#include
#include
逐個(gè)察看
(5): Vim /usr/include/c++/3.4.4/ bits/localefwd.h
發(fā)現(xiàn)模板類num_get聲明
template >
class num_get;
(6): Vim /usr/include/c++/3.4.4/ bits/locale_facets.h
在這個(gè)文件中發(fā)現(xiàn)num_get的實(shí)現(xiàn)
template
class num_get : public locale::facet
查找get方法
iter_type
get(iter_type __in, iter_type __end, ios_base& __io,
ios_base::iostate& __err, bool& __v) const
{ return this->do_get(__in, __end, __io, __err, __v); }
查找do_get方法
(7): Vim /usr/include/c++/3.4.4/ bits/locale_facets.tcc
發(fā)現(xiàn)
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 17. Bad bool parsing
template
_InIter
num_get<_CharT, _InIter>::
do_get(iter_type __beg, iter_type __end, ios_base& __io,
ios_base::iostate& __err, bool& __v) const
{
if (!(__io.flags() & ios_base::boolalpha))
{
// Parse bool values as long.
// NB: We can't just call do_get(long) here, as it might
// refer to a derived class.
long __l = -1;
__beg = _M_extract_int(__beg, __end, __io, __err, __l);
if (__l == 0 || __l == 1)
__v = __l;
Else
...
查找_M_extract_int 方法
終于找到
template
template
_InIter
num_get<_CharT, _InIter>::
_M_extract_int(_InIter __beg, _InIter __end, ios_base& __io,
ios_base::iostate& __err, _ValueT& __v) const
{
typedef char_traits<_CharT> __traits_type;
typedef typename numpunct<_CharT>::__cache_type __cache_type;
__use_cache<__cache_type> __uc;
const locale& __loc = __io._M_getloc();
const __cache_type* __lc = __uc(__loc);
const _CharT* __lit = __lc->_M_atoms_in;
....
分析_M_extract_int的關(guān)鍵代碼,
如下
…
int __base = __oct ? 8 : (__basefield == ios_base::hex ? 16 : 10);
…
const _ValueT __new_result = __result * __base
- __digit;
__overflow |= __new_result > __result;
__result = __new_result;
++__sep_pos;
__found_num = true;
…
根據(jù)以上代碼C++中的流轉(zhuǎn)換,沒有使用什么特別的技巧,在由字符串轉(zhuǎn)為數(shù)字時(shí),使用的也是查找字符*10(8,16)的方法,只是這個(gè)過程中多了很多步我們想不到的安全驗(yàn)證。
總算搞明白了,sgi真不是給人看得,你也可以了解float類型是怎么實(shí)現(xiàn)的,參考_M_extract_float函數(shù)。
string str(8,'\0');
strncpy(&str.at(0),"1234567",7);
cout << lexical_cast
結(jié)果運(yùn)行的時(shí)候發(fā)生如下異常
terminate called after throwing an instance of 'boost::bad_lexical_cast'
what(): bad lexical cast: source type value could not be interpreted as target
我們知道boost::lexical_cast最終使用的stringstream實(shí)現(xiàn)的數(shù)值類型轉(zhuǎn)換,所以,我們使用如下例子,做測試
stringstream ss;
ss << str;
ss >> result;
cout << "new Result: " << result << endl;
編譯運(yùn)行后,輸出
new Result: 1234567
可以正常顯示,好像沒有問題,
我們察看一下boost的源代碼
vim /usr/include/boost/lexical_cast.hpp
察看lexical_cast函數(shù)
template
Target lexical_cast(Source arg)
{
detail::lexical_stream
Target result;
if(!(interpreter << arg && interpreter >> result))
throw_exception(bad_lexical_cast(typeid(Target), typeid(Source)));
return result;
}
可見lexical_cast函數(shù)非常簡單,就是具體執(zhí)行operator<<和operator>>兩個(gè)操作,只要這兩個(gè)操作有一個(gè)失敗就拋出一個(gè)異常,為了確認(rèn)是那步出的錯(cuò),我們在程序中手工執(zhí)行這兩個(gè)操作。代碼如下
detail::lexical_stream
int result;
if(!(interpreter << str ))
{
cout << "Error 1" << endl;
}
if(!(interpreter >> result))
{
cout << "Error 2" << endl;
}
cout << result << endl;
編譯運(yùn)行后輸出
Error 2
從這里我們知道,lexical_cast是在執(zhí)行輸出流的時(shí)候發(fā)生的問題,察看detail的operator>>函數(shù),考試.大提示其源代碼如下
template
bool operator>>(InputStreamable &output)
{
return !is_pointer
stream >> output &&
(stream >> std::ws).eof();
}
根據(jù)以上代碼和我們使用stringstream做的測試,基本上可以確定在stream>>output(包括次步)都是正確的,可能出現(xiàn)問題的是(stream >> std::ws).eof();
這里解釋下std::ws和stringstring::eof()函數(shù)
Std::ws函數(shù)聲明在
/usr/include/c++/3.4.4/bits/istream.tcc
源代碼如下
// 27.6.1.4 Standard basic_istream manipulators
template
basic_istream<_CharT,_Traits>&
ws(basic_istream<_CharT,_Traits>& __in)
{
typedef basic_istream<_CharT, _Traits> __istream_type;
typedef typename __istream_type::__streambuf_type __streambuf_type;
typedef typename __istream_type::__ctype_type __ctype_type;
typedef typename __istream_type::int_type __int_type;
const __ctype_type& __ct = use_facet<__ctype_type>(__in.getloc());
const __int_type __eof = _Traits::eof();
__streambuf_type* __sb = __in.rdbuf();
__int_type __c = __sb->sgetc();
while (!_Traits::eq_int_type(__c, __eof)
&& __ct.is(ctype_base::space, _Traits::to_char_type(__c)))
__c = __sb->snextc();
if (_Traits::eq_int_type(__c, __eof))
__in.setstate(ios_base::eofbit);
return __in;
}
主要作用是過濾輸入流中的空格,\n\r等字符。stream >> std::ws目的就是把輸入流中轉(zhuǎn)換完整形后的剩余流內(nèi)容(假如有的話)寫入std::ws,當(dāng)然只能寫入其中的空格和\n\r等字符。
stringstring::eof()函數(shù)參考 http://www.cppreference.com/wiki/io/eof 部分
該函數(shù)的主要作用是,如果到達(dá)流的結(jié)束位置返回true,否則返回false
根據(jù)以上信息,我們編寫測試用例
stringstream ss;
ss << str;
ss >> result;
cout << "new Result: " << result << endl;
cout << ss.eof() << endl;
cout << (ss >> std::ws).eof() << endl; 編譯運(yùn)行后輸出
new Result: 1234567
0
0
由此可見,雖然我們使用ss時(shí),可以輸出想要的正確結(jié)果,但是我們?nèi)鄙僮詈蟮陌踩?yàn)證,而boost::lexical_cast就做了這方面的驗(yàn)證。
其實(shí)例子中的’\0’在開始的時(shí)候,起了不小的誤導(dǎo)作用,開始以為是boost::lexical_cast無法處理最后末尾是’\0’的字符串,到現(xiàn)在其實(shí)不然,我們把’\0’轉(zhuǎn)換為’a’字符一樣會(huì)出現(xiàn)這種問題,但是我們使用’\n’,’\r’和空格等字符就不會(huì)出現(xiàn)這種問題,現(xiàn)在我們知道其根源就是在字符轉(zhuǎn)換過程中輸入流沒有輸入全部字符,所以流的結(jié)束標(biāo)志EOF,一直為0。
其實(shí)在上面的應(yīng)用中我們不能一直認(rèn)為boost::lexical_cast的方法一定是好的。在我們編成過程中,常見的轉(zhuǎn)換是把一段字符串中含有數(shù)字和字母的字符串中的數(shù)字串轉(zhuǎn)換為整形,這樣的如果我們使用boost::lexical_cast的話,永遠(yuǎn)得不到正確結(jié)果了,每次都會(huì)有異常拋出,這時(shí)候我們可以使用stringstream,轉(zhuǎn)換后不判斷eof(),這樣就可以得到我們想要的整數(shù)。
在上面的測試中,突然想到一個(gè)變態(tài)的想法,STL中的字符串轉(zhuǎn)為整形的流實(shí)現(xiàn)是怎么做的,不過SGI的STL真夠難堪的。
大體查找過程如下
(1): Vim /usr/include/c++/3.4.4/sstream
發(fā)現(xiàn)引用了istream
(2): Vim /usr/include/c++/3.4.4/ istream
發(fā)現(xiàn)operator<<(int)的實(shí)現(xiàn)在bits/istream.tcc文件中
(3): Vim /usr/include/c++/3.4.4/ bits/istream.tcc
發(fā)現(xiàn)const __num_get_type& __ng = __check_facet(this->_M_num_get);__ng.get(*this, 0, *this, __err, __l);所以查找__num_get_type類型中的get函數(shù),同時(shí)發(fā)現(xiàn)istream.tcc中的#include
(4): Vim /usr/include/c++/3.4.4/locale
發(fā)現(xiàn)這個(gè)文件中包括以下頭文件
#include
#include
#include
#include
逐個(gè)察看
(5): Vim /usr/include/c++/3.4.4/ bits/localefwd.h
發(fā)現(xiàn)模板類num_get聲明
template
class num_get;
(6): Vim /usr/include/c++/3.4.4/ bits/locale_facets.h
在這個(gè)文件中發(fā)現(xiàn)num_get的實(shí)現(xiàn)
template
class num_get : public locale::facet
查找get方法
iter_type
get(iter_type __in, iter_type __end, ios_base& __io,
ios_base::iostate& __err, bool& __v) const
{ return this->do_get(__in, __end, __io, __err, __v); }
查找do_get方法
(7): Vim /usr/include/c++/3.4.4/ bits/locale_facets.tcc
發(fā)現(xiàn)
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 17. Bad bool parsing
template
_InIter
num_get<_CharT, _InIter>::
do_get(iter_type __beg, iter_type __end, ios_base& __io,
ios_base::iostate& __err, bool& __v) const
{
if (!(__io.flags() & ios_base::boolalpha))
{
// Parse bool values as long.
// NB: We can't just call do_get(long) here, as it might
// refer to a derived class.
long __l = -1;
__beg = _M_extract_int(__beg, __end, __io, __err, __l);
if (__l == 0 || __l == 1)
__v = __l;
Else
...
查找_M_extract_int 方法
終于找到
template
template
_InIter
num_get<_CharT, _InIter>::
_M_extract_int(_InIter __beg, _InIter __end, ios_base& __io,
ios_base::iostate& __err, _ValueT& __v) const
{
typedef char_traits<_CharT> __traits_type;
typedef typename numpunct<_CharT>::__cache_type __cache_type;
__use_cache<__cache_type> __uc;
const locale& __loc = __io._M_getloc();
const __cache_type* __lc = __uc(__loc);
const _CharT* __lit = __lc->_M_atoms_in;
....
分析_M_extract_int的關(guān)鍵代碼,
如下
…
int __base = __oct ? 8 : (__basefield == ios_base::hex ? 16 : 10);
…
const _ValueT __new_result = __result * __base
- __digit;
__overflow |= __new_result > __result;
__result = __new_result;
++__sep_pos;
__found_num = true;
…
根據(jù)以上代碼C++中的流轉(zhuǎn)換,沒有使用什么特別的技巧,在由字符串轉(zhuǎn)為數(shù)字時(shí),使用的也是查找字符*10(8,16)的方法,只是這個(gè)過程中多了很多步我們想不到的安全驗(yàn)證。
總算搞明白了,sgi真不是給人看得,你也可以了解float類型是怎么實(shí)現(xiàn)的,參考_M_extract_float函數(shù)。

