从C++与Unicode说开去

以前遇到过这么一个问题,用C++序列化一个wstring。程序很简单,因为C++有wofstream:

std::wstring str   =   L"s";
std::wofstream ofile("F://test.txt");
ofile<<str; 
 

我使用的编译器是VC9,wchar_t是2字节。那么test.txt就应该是unicode的宽字节,其大小应该是2字节。但是实际的结果却是1字节,串并没有按照宽字节的方式保存。这并非是编译器的问题,其结果是符合C++标准的!其原因在于流在写入的时候会调用codecvt这个模板类来对流的内容处理,而C++标准只规定了两种codecvt:

codecvt<char, char, mbstate_t>

codecvt<wchar_t, char, mbstate_t>

模板的第一个参数是内部类型,第二个是外部类型。

乍一看,如果使用默认的codecvt,输出必然是char,也就是单字节的。如果想要输出宽字节的流,必须得自行定义一个什么都不做的codecvt<wchar_t, wchar_t, mbstate_t> (其实这个理解是不对的,后面会解释)。

把这个codecvt配置到locale里面,再用locale配置流对象,那么输出就会是2字节了。问题是输出的字符是何种编码呢?回答是:implement-dependent。编码方式取决于你的编译器中对于wchar_t这个类型是何种编码的(注意wchar_t是标准所规定是keyword,如果你的编译器很老比如VC6,那么它会是一个typedef),如果你的编译器中wchar_t是2字节,那么编码应该是UCS-2,如果是4字节,那么应该是UCS-4,输出也就为4字节,不再是2字节了。不过我们可以肯定的是,此编码不会是变长的编码如UTF-8(这是一个变长的编码以兼容ASCII,UTF-8中的ACSII部分的字符编码是1字节的。这么做并不是信息论中的节省平均编码长度,而仅仅是达到兼容的目的)。关于编码的知识网上有很多,不太了解的可以搜索一下。

看似codecvt这个facet功能很弱,其实不然。codecvt中的内部类型和外部类型并非指的是输入类型和输出类型,一个codecvt<wchar_t, char, mbstate_t>同样可以实现unicode的输出(boost中有个UCS-4到UTF-8的转换就是codecvt<wchar_t, char, mbstate_t>)。真正的编码转换部分是通过override其虚函数实现的,并不是指定模板参数实现的。模板参数的两个type仅仅达到控制流的最小写入字长,在理论上,实现合适的编码转换函数,codecvt<char, char, mbstate_t>就可以完成所有的编码转换,因为它处理的流都是最小长度,对于那些宽字符,只要有足够的状态记录,就可以完成编码转换的目的(比如写入多个char凑成一个wchar_t,boost的那个codecvt就是这样,通过写入char来输出UTF-8。当然这也是最好的选择,因为UTF-8中有一字节的字符。如果用wchar_t,在输出那些一个字符长度的字符时,反而麻烦)。

实际上C++本身或者说C++ Standard并没有提供编码转换的这么一个功能,他所提供的只是一个框架,通过继承codecvt,override他的虚函数去实现你自己需要的编码转换,使得你可以让这个功能和其他C++库函数结合起来。C++标准规定的两个codecvt仅仅保证内部类型的流化,所谓内部类型就是C++语言本身使用的字符类型,它可以是单字节的char也可以是多字节的wchar_t。至于如何在不同编码之间转换需要你自己定义,默认的两个codecvt只是ctype意义上的widen和narrow而已。

所以如果你需要这种编码转换功能,最好就是使用现成的库(http://www.dinkumware.com/)或函数(iconv)。如果你够强,自己继承codecvt实现也是可以的:)

这是boost的UCS-4 to UTF-8的文档

http://www.boost.org/libs/serialization/doc/codecvt.html

里面的方法就是标准的C++方式,其中有一行是

std::locale utf8_locale(old_locale,new utf8_codecvt_facet<ucs4_t>);

意思是构造一个locale,除了第二个facet,其他都保持默认。而第二个facet也就是你传递进去的那个实现了编码转换。facet通过引用计数来释放,所以这种传递方式是没问题的。其中的locale,facet都是C++标准库的国际化组件,其组织结构是:locale是一组facet的容器,而codecvt又是facet的一个子类。根据不同的功能,facet被分成几组categories。具体的可以看下ISO 14882 ,反正相关的资料非常少,CPL里面都只有寥寥几笔。

顺便发点牢骚,感叹商业运作和非商业的区别:自由能够带来繁荣,专治能够带来规范和整洁。我感觉非商业运作的东西有个很大的缺陷:没有人去做无趣的事情。比如就这篇BLOG所讲,C++的国际化有个很不错的框架,但是却没有内容!ACE是个很不错的wrapper,但是没有文档!就连出的几本书中的有些内容也跟现在版本的ACE对不上了,一些书中的类干脆被移除了。想用ACE?看源代码吧。linux下想发挥硬件性能?驱动还不一定装的上。总之那些重复性的,繁复的事情很少会有人去做。无报酬的事情,大家自然都喜欢那些他们感兴趣的事情。商业运作的东西,就本身而言不一定比得上非商业运作的(估计只在软件业有这种现象,软件的成本很特殊),但是绝对易于上手,文档全面。图形界面的QT和wxWidgets就所实现的功能来讲,没有太大差距,但为什么QT比wxWidgets更广泛呢,我想就在于商业运作的推广上,QT有更丰富的资源。当然了这一切都是你花钱买来的,那些花的钱有很大一部分支付给了那些做了许多“无趣的事情”的人。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值