1 C++标准库中与字符集转换相关的类

1.1 std::use_local和facet

C++中的std::local每个本地化环境对象至少包含了以下的标准facet,

除了这些标准化的facet之外,我们也可以自定义一个facet对象,上述标准化的facet对象都有自己的特定功能,如果我们要访问一个std::local对象中特定的facet对象则需要使用模板方法std::use_facet

template< class Facet >
const Facet& use_facet( const std::locale& loc );

那么使用std::use_facet的作用是什么呢?使用std::use_facet主要是把一个facet类实例化成为了一个对象,然后就可以使用这个对象的成员函数。

1.2 std::codecvt

在1.1节中我们可以看到,其实std::codecvt就是一个标准的facet,其原型如下

template<
    class InternT,
    class ExternT,
    class StateT
> class codecvt;

其中,InternT表示内部编码,ExternT表示外部编码,StateT则是不同转换方式的标志。比如说std::codecvt<wchar_t, char, std::mbstate_t>,其内部编码就是wchar_t,而外部编码就是char,而std::codecvt<wchar_t, char, std::mbstate_t>就是一个在系统原生宽和窄字符集进行转换的一个facet。

std::codecvt有以下两个成员函数:

  • out:调用do_out虚函数,而do_out虚函数主要是将字符串从内部编码InternT转为外部编码ExternT
  • in:调用do_in虚函数,而do_in虚函数则主要是将字符串从外部编码ExternT转为内部编码InternT

所以我们如果调用std::codecvt<wchar_t, char, std::mbstate_t>的成员函数out,则可以将字符串从wchar_t转换为char;调用成员函数in则可以将字符串从char转化为wchar_t。

1.3 std::wstring_convert

在字符集的转换过程中还会用到std::wstring_convert,其函数模板如下

template< class Codecvt,
          class Elem = wchar_t,
          class Wide_alloc = std::allocator<Elem>,
          class Byte_alloc = std::allocator<char> >
class wstring_convert;

其函数模板的作用主要是进行窄字符std::string与宽字符std::wstring之间的转换,其有常用的两个成员函数

  • from_bytes:将byte string转换为wide string
  • to_bytes:将wide string转换为byte string

不过遗憾的是,这个函数在C++11启用,但是在C++17就被弃用了,所以在使用下述代码时应选择正确版本的编译器。

2 使用std::use_local和std::codecvt进行跨平台gbk和utf8字符集转换

在下述代码中,先使用std::use_facet实例化std::codecvt<wchar_t, char, std::mbstate_t>对象,然后再调用其outin成员函数进行宽窄字符的转换,然后使用std::wstring_convert完成字符集的转换,示例的代码如下,可以对照代码以及第1节的描述梳理思路

#include <iostream>

#include <string>
#include <vector>
#include <locale>
#include <codecvt>
using namespace std;


std::string gb2312_to_utf8(std::string const& strGb2312)
{
    std::vector<wchar_t> buff(strGb2312.size());
#ifdef _MSC_VER
    std::locale loc("zh-CN");
#else
    std::locale loc("zh_CN.GB18030");
#endif
    wchar_t* pwszNext = nullptr;
    const char* pszNext = nullptr;
    mbstate_t state = {};
    int res = std::use_facet<std::codecvt<wchar_t, char, mbstate_t> >
        (loc).in(state,
            strGb2312.data(), strGb2312.data() + strGb2312.size(), pszNext,
            buff.data(), buff.data() + buff.size(), pwszNext);

    if (std::codecvt_base::ok == res)
    {
        std::wstring_convert<std::codecvt_utf8<wchar_t>> cutf8;
        return cutf8.to_bytes(std::wstring(buff.data(), pwszNext));
    }

    return "";

}

std::string utf8_to_gb2312(std::string const& strUtf8)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> cutf8;
    std::wstring wTemp = cutf8.from_bytes(strUtf8);
#ifdef _MSC_VER
    std::locale loc("zh-CN");
#else
    std::locale loc("zh_CN.GB18030");
#endif
    const wchar_t* pwszNext = nullptr;
    char* pszNext = nullptr;
    mbstate_t state = {};

    std::vector<char> buff(wTemp.size() * 2);
    int res = std::use_facet<std::codecvt<wchar_t, char, mbstate_t> >
        (loc).out(state,
            wTemp.data(), wTemp.data() + wTemp.size(), pwszNext,
            buff.data(), buff.data() + buff.size(), pszNext);

    if (std::codecvt_base::ok == res)
    {
        return std::string(buff.data(), pszNext);
    }
    return "";
}


int main()
{

    std::string str_utf8 = u8"你好";
    std::string str_gbk = utf8_to_gb2312(str_utf8);

    std::cout << str_gbk << std::endl;

    std::string str_res = gb2312_to_utf8(str_gbk);
    std::cout << str_res << std::endl;

    return 0;
}

输出结果:

你好
浣犲ソ

参考链接