C++流实现内幕---由boost::lexical_cast引发的一个问题

中午同事碰见一个关于使用boost::lexical_cast产生异常的问题,关键代码如下

  1. string str(8,'/0');
  2. strncpy(&str.at(0),"1234567",7);
  3. cout << lexical_cast<int>(str) << endl;

结果运行的时候发生如下异常

  1. terminate called after throwing an instance of 'boost::bad_lexical_cast'
  2.   what():  bad lexical cast: source type value could not be interpreted as target

我们知道boost::lexical_cast最终使用的stringstream实现的数值类型转换,所以,我们使用如下例子,做测试

  1. stringstream ss;
  2. ss << str;
  3. ss >> result;
  4. cout << "new Result: " << result << endl;

编译运行后,输出

new Result: 1234567

可以正常显示,好像没有问题,

我们察看一下boost的源代码

vim /usr/include/boost/lexical_cast.hpp

察看lexical_cast函数

  1. template<typename Target, typename Source>
  2.     Target lexical_cast(Source arg)
  3.     {
  4.         detail::lexical_stream<Target, Source> interpreter;
  5.         Target result;
  6.  
  7.         if(!(interpreter << arg && interpreter >> result))
  8.             throw_exception(bad_lexical_cast(typeid(Target), typeid(Source)));
  9.         return result;
  10. }

可见lexical_cast函数非常简单,就是具体执行operator<<和operator>>两个操作,只要这两个操作有一个失败就抛出一个异常,为了确认是那步出的错,我们在程序中手工执行这两个操作。代码如下

  1. detail::lexical_stream<int, string> interpreter;
  2.     int result;
  3.  
  4.     if(!(interpreter << str ))
  5.     {
  6.         cout << "Error 1" << endl;
  7.     }
  8.     if(!(interpreter >> result))
  9.     {
  10.         cout << "Error 2" << endl;
  11.     }
  12. cout << result << endl;

编译运行后输出

Error 2

从这里我们知道,lexical_cast是在执行输出流的时候发生的问题,察看detail的operator>>函数,其源代码如下

  1. template<typename InputStreamable>
  2.             bool operator>>(InputStreamable &output)
  3.             {
  4.                 return !is_pointer<InputStreamable>::value &&
  5.                        stream >> output &&
  6.                        (stream >> std::ws).eof();
  7.             }

根据以上代码和我们使用stringstream做的测试,基本上可以确定在stream>>output(包括次步)都是正确的,可能出现问题的是(stream >>
std::ws).eof();

这里解释下std::ws和stringstring::eof()函数

Std::ws函数声明在

/usr/include/c++/3.4.4/bits/istream.tcc

源代码如下

  1. // 27.6.1.4 Standard basic_istream manipulators
  2.   template<typename _CharT, typename _Traits>
  3.     basic_istream<_CharT,_Traits>&
  4.     ws(basic_istream<_CharT,_Traits>& __in)
  5.     {
  6.       typedef basic_istream<_CharT, _Traits>        __istream_type;
  7.       typedef typename __istream_type::__streambuf_type __streambuf_type;
  8.       typedef typename __istream_type::__ctype_type __ctype_type;
  9.       typedef typename __istream_type::int_type     __int_type;
  10.  
  11.       const __ctype_type& __ct = use_facet<__ctype_type>(__in.getloc());
  12.       const __int_type __eof = _Traits::eof();
  13.       __streambuf_type* __sb = __in.rdbuf();
  14.       __int_type __c = __sb->sgetc();
  15.  
  16.       while (!_Traits::eq_int_type(__c, __eof)
  17.          && __ct.is(ctype_base::space, _Traits::to_char_type(__c)))
  18.     __c = __sb->snextc();
  19.  
  20.        if (_Traits::eq_int_type(__c, __eof))
  21.      __in.setstate(ios_base::eofbit);
  22.       return __in;
  23. }

主要作用是过滤输入流中的空格,/n/r等字符。stream
>> std::ws目的就是把输入流中转换完整形后的剩余流内容(假如有的话)写入std::ws,当然只能写入其中的空格和/n/r等字符。

stringstring::eof()函数参考 http://www.cppreference.com/wiki/io/eof
部分

该函数的主要作用是,如果到达流的结束位置返回true,否则返回false

根据以上信息,我们编写测试用例

  1. stringstream ss;
  2. ss << str;
  3. ss >> result;
  4. cout << "new Result: " << result << endl;
  5. cout << ss.eof() << endl;
  6. cout << (ss >> std::ws).eof() << endl;

编译运行后输出

new Result: 1234567

0

0

由此可见,虽然我们使用ss时,可以输出想要的正确结果,但是我们缺少最后的安全验证,而boost::lexical_cast就做了这方面的验证。

其实例子中的’/0’在开始的时候,起了不小的误导作用,开始以为是boost::lexical_cast无法处理最后末尾是’/0’的字符串,到现在其实不然,我们把’/0’转换为’a’字符一样会出现这种问题,但是我们使用’/n’,’/r’和空格等字符就不会出现这种问题,现在我们知道其根源就是在字符转换过程中输入流没有输入全部字符,所以流的结束标志EOF,一直为0。

其实在上面的应用中我们不能一直认为boost::lexical_cast的方法一定是好的。在我们编成过程中,常见的转换是把一段字符串中含有数字和字母的字符串中的数字串转换为整形,这样的如果我们使用boost::lexical_cast的话,永远得不到正确结果了,每次都会有异常抛出,这时候我们可以使用stringstream,转换后不判断eof(),这样就可以得到我们想要的整数。

       在上面的测试中,突然想到一个变态的想法,STL中的字符串转为整形的流实现是怎么做的,不过SGI的STL真够难堪的。

       大体查找过程如下

       (1): Vim
/usr/include/c++/3.4.4/sstream

发现引用了istream

       (2):  Vim
/usr/include/c++/3.4.4/ istream

发现operator<<(int)的实现在bits/istream.tcc文件中

       (3): Vim
/usr/include/c++/3.4.4/ bits/istream.tcc

发现const
__num_get_type& __ng = __check_facet(this->_M_num_get);__ng.get(*this,
0, *this, __err, __l);所以查找__num_get_type类型中的get函数,同时发现istream.tcc中的#include <locale> 比较陌生,同时在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

发现这个文件中包括以下头文件

  1. #include <bits/localefwd.h>
  2. #include <bits/locale_classes.h>
  3. #include <bits/locale_facets.h>
  4. #include <bits/locale_facets.tcc>

逐个察看

       (5):  Vim
/usr/include/c++/3.4.4/ bits/localefwd.h

发现模板类num_get声明

template<typename _CharT, typename
_InIter = istreambuf_iterator<_CharT> >

   
class num_get;

 

       (6): Vim
/usr/include/c++/3.4.4/
bits/locale_facets.h

在这个文件中发现num_get的实现

template<typename _CharT, typename
_InIter>

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

发现

  1. // _GLIBCXX_RESOLVE_LIB_DEFECTS
  2.   // 17.  Bad bool parsing
  3.   template<typename _CharT, typename _InIter>
  4.     _InIter
  5.     num_get<_CharT, _InIter>::
  6.     do_get(iter_type __beg, iter_type __end, ios_base& __io,
  7.            ios_base::iostate& __err, bool& __v) const
  8.     {
  9.       if (!(__io.flags() & ios_base::boolalpha))
  10.         {
  11.       // Parse bool values as long.
  12.           // NB: We can't just call do_get(long) here, as it might
  13.           // refer to a derived class.
  14.       long __l = -1;
  15.           __beg = _M_extract_int(__beg, __end, __io, __err, __l);
  16.       if (__l == 0 || __l == 1)
  17.         __v = __l;
  18.       Else
  19. ...

查找_M_extract_int
方法

终于找到

  1.  template<typename _CharT, typename _InIter>
  2.     template<typename _ValueT>
  3.       _InIter
  4.       num_get<_CharT, _InIter>::
  5.       _M_extract_int(_InIter __beg, _InIter __end, ios_base& __io,
  6.              ios_base::iostate& __err, _ValueT& __v) const
  7.       {
  8.         typedef char_traits<_CharT>         __traits_type;
  9.     typedef typename numpunct<_CharT>::__cache_type __cache_type;
  10.     __use_cache<__cache_type> __uc;
  11.     const locale& __loc = __io._M_getloc();
  12.     const __cache_type* __lc = __uc(__loc);
  13. const _CharT* __lit = __lc->_M_atoms_in;
  14. ....

分析_M_extract_int的关键代码,

如下

  1. int __base = __oct ? 8 : (__basefield == ios_base::hex ? 16 : 10);
  2.           const _ValueT __new_result = __result * __base
  3.                                          - __digit;
  4.             __overflow |= __new_result > __result;
  5.             __result = __new_result;
  6.             ++__sep_pos;
  7.             __found_num = true;

根据以上代码C++中的流转换,没有使用什么特别的技巧,在由字符串转为数字时,使用的也是查找字符*10(8,16)的方法,只是这个过程中多了很多步我们想不到的安全验证。

 

总算搞明白了,sgi真不是给人看得,你也可以了解float类型是怎么实现的,参考_M_extract_float函数。

时间: 2024-08-03 10:55:08

C++流实现内幕---由boost::lexical_cast引发的一个问题的相关文章

c++数据类型万能转换器boost::lexical_cast .

boost::lexical_cast为数值之间的转换(conversion)提供了一揽子方案,比如:将一个字符串"123"转换成整数123,代码如下: string s = "123";   int a = lexical_cast<int>(s);  这种方法非常简单,笔者强烈建议大家忘掉std诸多的函数,直接使用boost:: lexical_cast.如果转换发生了意外,lexical_cast会抛出一个bad_lexical_cast异常,因此

使用boost的deadline_timer实现一个异步定时器

概述 最近在工作上需要用到定时器,然后看到boost里面的deadline_timer可以实现一个定时器,所以就直接将其封装成了ATimer类,方便使用,ATimer有以下优点: 可以支持纳秒.毫秒.秒.分.小时定时. 可以随时停止定时器. 支持单次调用. 因为使用了deadline_timer,所以定时比较准确. ATimer和Qt的QTimer使用方法类似,若没有类似的Timer类,使用最原始的方法,我们的代码可能会是这样的: m_timerThread = std::thread([thi

oracle数据库library cache lock引发的一个问题解决办法

美女同事说某个客户有个问题,系统出现了大量的library cache lock. 导致业务严重受阻,具体表现是所有访问某个表的SQL语句都会挂起. 首先我们来看hanganalyze 的结果: PORADEBUG END ORIGINATING INST:1 SERIAL:0 PID:38076802 ******************************************************************** Found 341 objects waiting fo

rm -rf/ 又引发了一个血案

Marco Marsala是一家小型主机托管公司的老板,但是他最近遇到了一个天大的麻烦--由于脚本错误,他不慎删光了所有客户的数据.更糟糕的是,由于Bash脚本代码中包含了一行变量未定义的"rm -rf {foo}/{bar}",连备份也连带着被干掉了--而在通常情况下,备份网络理应和正常的生产力基础设施隔离开的. 这一错误源自 Ansible 上糟糕的代码设计,这款 Linux 实用工具被用于在多台不同服务器上自动执行脚本. 开发者解释到,实际参数应该是"rm -rf {f

控件 System.Windows.Forms.LinkLabel 在设计器中引发了一个未经处理的异常,已被禁用。 异常:不允许所请求的注册表访问权。

问题描述 解决方案 解决方案二:你就拖了几个link就出这个问题?什么都没做?也许你该装系统了解决方案三:估计是电脑感染了360系列非法流氓病毒所致.要么想办法杀掉360系列非法流氓病毒,要么重装系统.解决方案四:以管理员身份运行VS解决方案五:以管理员身份运行VS正解解决方案六:求解以管理员身份仍然无法运行啊解决方案七:以管理员身份是无法运行啊.

boost uuid

uuid: uuid库是一个小的使用工具,可以表示和生成UUID UUID是University Unique Identifier的缩写,它是一个128位的数字(16字节),不需要有一个中央认证机构就可以创建全国唯一的标示符.别名:GUID uuid位于名字空间boost::uuisd,没有集中的头文件,把功能分散在了若干小文件中,因此为了使用uuid组件,需要包含数个头文件,即:#include <boost/uuid/uuid.hpp>#include <boost/uuid/uu

背景建模技术(八):bgslibrary_vs2010_mfc中boost的安装与配置

一.boost的下载与安装 在玩BGS Library时,有一个MFC的项目,在编译的过程中出现如下图的错误提示: 即: 1>e:\bgslibrary-master\vs2010mfc\src\stdafx.h(50): fatal error C1083: Cannot open include file: 'boost/lexical_cast.hpp': No such file or directory 根本原因在于没有安装和配置boost,下面对bgslibrary_vs2010_m

实战准标准库Boost(2)测试Boost配置的Hello World程序

1. 配置环境 请先按照<Boost C++ Libs -- (1)配置Boost的VS2008开发环境>一文在Visual Studio中配置开发环境. 2. 源码 #include <boost/lexical_cast.hpp> #include <iostream> using namespace std; int main() { using boost::lexical_cast; int a=lexical_cast<int>("12

boost库的常用组件的使用(ZT)

1.boost::any boost::any是一种通用的数据类型,可以将各种类型包装后统一放入容器内 最重要的它是类型安全的.有点象COM里面的variant. 使用方法: any::type() 返回包装的类型 any_cast可用于any到其他类型的转化    #include  < boost / any.hpp >  void  test_any() { typedef std::vector < boost::any >  many; many a; a.push_ba