页面

分类

C++ locale 一例

2016/5/31, by wingfire ; 分类: 计算机技术, 代码相关; 0 comments

csv格式的数据是由逗号分隔的,C++标准库的iostream在读取时则是以空白分隔的。自然的,希望通过设置iostream,指定分隔符。然而在istream上并没有直接的方法来设置,也没有相关的manipulator来履行相关操作。实际上,istream的输入空白符控制是直接通过locale来完成的。

C++的locale一向恶名昭著,如果对C++ locale缺乏系统的了解,大概连怎么Google都是问题。网上的不少回答都是不可能。搜到一个CSDN上的帖子,通过hack ctype::table()返回的表:

     ifstream in;

       const ctype<char>& _Fac=_USE(in.getloc(),ctype<char>);
       WORD* pCltab=*(WORD**)((BYTE*)&_Fac+16); // 直接位移取mask地址
       pCltab[',']|=_SPACE; // 把','也加入分隔条件`

但是这个实现问题是非常多的,那个_Fac+16更是黑手,根本无法移植。

ctype实际上是可以被定制化的,在这里有个例子 http://en.cppreference.com/w/cpp/locale/ctype

代码:

        #include <iostream>
        #include <locale>
        #include <sstream>

        struct csv_whitespace : std::ctype<wchar_t>
        {
            bool do_is(mask m, char_type c) const
            {
                if ((m & space) && c == L' ') {
                    return false; // space will NOT be classified as whitespace
                }
                if ((m & space) && c == L',') {
                    return true; // comma will be classified as whitespace
                }
                return ctype::do_is(m, c); // leave the rest to the parent class
            }
        };

        int main()
        {
            std::wstring in = L"Column 1,Column 2,Column 3\n123,456,789";
            std::wstring token;

            std::wcout << "default locale:\n";
            std::wistringstream s1(in);
            while (s1 >> token) {
                std::wcout << "  " << token << '\n';
            }

            std::wcout << "locale with modified ctype:\n";
            std::wistringstream s2(in);
            csv_whitespace* my_ws = new csv_whitespace; // note: this allocation is not leaked
            s2.imbue(std::locale(s2.getloc(), my_ws));
            while (s2 >> token) {
                std::wcout << "  " << token << '\n';
            }
        }

我需要的处理char,看上去我只需要写个std::ctype,然后重写do_is就好了。很不幸,这行不通。

标准中定义了std::ctype的特化版本,而这个特化版本是没有do_XXX系列的函数。不太清楚为什么标准是这样的,估计可能为了性能。既然不能定制,是不是只能hack了呢?如果hack,怎么做才是比较好的呢?回头看一下CSDN的那段代码: WORD pCltab=(WORD*)((BYTE)&_Fac+16); 这段代码是为了获得ctype内部的转换表,实际上有对应的函数:table().但是table的返回值是const指针类型,不允许修改内部值。强行const_cast然后修改是有风险的:

  1. const数据可能是共享的,修改以后可能改变系统默认的行为,很危险。
  2. const数据可能是写保护的,并不能修改。

在Linux + gcc环境就属于情况2。幸运的是,可以通过C++标准库的API来达到目的。总体步骤是:

  1. 以某个ctype为模板,复制创建一个table
  2. 修改table以符合需要
  3. 以table为参数创建新的ctype对象
  4. 以新ctype为参数创建locale

示例代码:

        using namespace std;
        std::locale customized_locale() {
            std::locale loc("C");
            static ctype<char>::mask s_table[ctype<char>::table_size];
            auto table = use_facet<ctype<char>>(loc).table();
            std::copy(table, table + ctype<char>::table_size, s_table);


            s_table[','] = ctype<char>::space;

            // ' '  (0x20)  space(SPC)
            //'\t'  (0x09)  horizontal tab(TAB)
            //'\n'  (0x0a)  newline(LF)
            //'\v'  (0x0b)  vertical tab(VT)
            //'\f'  (0x0c)  feed(FF)
            //'\r'  (0x0d)  carriage return (CR)
            s_table[' '] &= ~ctype<char>::space;
            s_table['\t'] &= ~ctype<char>::space;
            s_table['\v'] &= ~ctype<char>::space;
            s_table['\f'] &= ~ctype<char>::space;
            // Don't override \r & \n

            ctype<char>*  fac = new ctype<char>(s_table);
            return std::locale(loc, fac);
        }

vc 和 gcc下都成功运行

添加评论:

 
 the email would not displayed
 

您可以使用 Markdown 语法。

您必须启用浏览器的 JavaScript 功能才能发表评论。