我在 MSVC 上尝试过
std::codecvt
并遇到了多字节字符编码的问题 - 它无法从有效的多字节序列转换回来,即使在编码宽字符时可以生成这些序列:
std::locale loc(".950");
using cvttype = std::codecvt<wchar_t, char, std::mbstate_t>;
const auto &cvt = std::use_facet<cvttype>(loc);
std::wstring_convert<cvttype> conv(&cvt);
auto bytes = conv.to_bytes(L"\u4F53"); // 体
auto str = conv.from_bytes(bytes); // range_error("bad conversion")
是的,我知道从区域设置中获取一个方面会导致此处的所有权问题,但即使在通过代理方面或手动调用
std::codecvt::in
时也会出现此问题。
我已经使用其他几个代码页(例如 949 或 52936)对此进行了测试,所有结果都相同。我以前用过
std::codecvt_utf8
,没有这样的问题;只有基于语言环境的 std::codecvt
在这里失败了。
为什么会有这种行为?可以修复吗?
使用 VS 2017 编译。
原来我的程序文件夹中有msvcp140d.dll,它比系统的旧4年。无论它是简单的错误还是与系统不兼容,删除它都可以解决问题。
如果有人偶然发现同样的问题,而这不是原因,您可以先检查 C 转换是否有效:
std::setlocale(LC_ALL, ".950");
std::mbstate_t state{};
wchar_t res;
std::size_t len = std::mbrtowc(&res, &bytes[0], bytes.size(), &state);
如果
res
获得有效字符,您可以跳过 std::codecvt
机制并仅在循环中使用 std::mbrtowc
。
如果您无法调用
setlocale
或想直接依赖 std::locale
实例,您仍然可以,尽管需要一些技巧:
// Use your compiler version and check every time you update!
#if _MSC_VER == 1916
using cvtvec_ptr_t = std::_Locinfo::_Cvtvec(std::codecvt<wchar_t, char, std::mbstate_t>::*);
// Output variable to receive the _Cvt member
static cvtvec_ptr_t cvtvec_ptr;
namespace
{
template <cvtvec_ptr_t Cvtvec>
struct get_private
{
get_private() noexcept
{
cvtvec_ptr = Cvtvec;
}
static get_private instance;
};
template <cvtvec_ptr_t Cvtvec>
get_private<Cvtvec> get_private<Cvtvec>::instance;
// Define an object of the type, passing the private member
template struct get_private<&std::codecvt<wchar_t, char, std::mbstate_t>::_Cvt>;
}
#endif
std::mbstate_t state{};
wchar_t res;
std::size_t len = _Mbrtowc(&res, &bytes[0], bytes.size(), &state, &(cvt.*cvtvec_ptr));
这里的技巧是使用显式模板实例化来引入一个全局变量,其初始值设定项将
cvtvec_ptr
设置为指向私有 std::codecvt<wchar_t, char, std::mbstate_t>::_Cvt
成员。这在技术上是有效的,但它使用了保留的标识符,这些标识符意味着在 std::codecvt::do_in
实现中使用,而不是在用户代码中使用。虽然这段代码可能会工作或无法编译,但显然不能保证。