5

这是使用std::codecvt_utf8<>facet 转换wchar_t为 UTF-8 的代码片段。使用 Visual Studio 2012,我的期望没有得到满足(请参阅代码末尾的条件)。我的期望错了吗?为什么?或者这是 Visual Studio 2012 库问题?

#include <locale>
#include <codecvt>
#include <cstdlib>

int main ()
{
    std::mbstate_t state = std::mbstate_t ();
    std::locale loc (std::locale (), new std::codecvt_utf8<wchar_t>);
    typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_type;
    codecvt_type const & cvt = std::use_facet<codecvt_type> (loc);

    wchar_t ch = L'\u5FC3';
    wchar_t const * from_first = &ch;
    wchar_t const * from_mid = &ch;
    wchar_t const * from_end = from_first + 1;

    char out_buf[1];
    char * out_first = out_buf;
    char * out_mid = out_buf;
    char * out_end = out_buf + 1;

    std::codecvt_base::result cvt_res
        = cvt.out (state, from_first, from_end, from_mid,
            out_first, out_end, out_mid);

    // This is what I expect:
    if (cvt_res == std::codecvt_base::partial
        && out_mid == out_end
        && state != 0)
        ;
    else
        abort ();
}

这里的期望是该out()函数一次输出一个字节的 UTF-8 转换,但上述if条件的中间在 Visual Studio 2012 中为 false。

更新

失败的是out_mid == out_endstate != 0条件。基本上,我希望至少产生一个字节,并将必要的状态存储在state变量中,以使 UTF-8 序列的下一个字节可产生。

4

2 回答 2

4

partial返回码的标准描述codecvt::do_out正是这样说的:

在表 83 中:

partial并非所有源字符都已转换

在 22.4.1.4.2[locale.codecvt.virtuals]/5 中:

返回:一个枚举值,如表 83 中总结的那样。返回值为partial,如果(from_next==from_end),则表明目标序列没有吸收所有可用的目标元素,或者在产生另一个目标元素之前需要额外的源元素。

在您的情况下,并非所有(零)源字符都被转换,这在技术上没有说明输出序列的内容(句子中的'if'子句没有输入),但一般来说,“目标序列没有吸收所有可用的目标元素”在这里谈论有效的多字节字符。它们是由生成的多字节字符序列的元素codecvt_utf8

有一个更明确的标准措辞会很好,但这里有两个间接证据:

一:旧 C 的宽到多字节转换函数std::wcsrtombs(其特定于语言环境的变体通常由codecvt::do_out系统提供的语言环境的现有实现调用)定义如下:

当下一个多字节字符超过要存储到 dst 指向的数组中的 len 个总字节数的限制时,转换停止 [...]。

第二,看看现有的实现codecvt_utf8:你已经探索过微软的,这是 libc++ 中的内容:codecvt_utf8::do_out这里ucs2_to_utf8在 Windows 和ucs4_to_utf8其他系统上调用,而 ucs2_to_utf8执行以下操作(我的评论):

        else if (wc < 0x0800)
        {
            // not relevant
        }
        else // if (wc <= 0xFFFF)
        {
            if (to_end-to_nxt < 3)
                return codecvt_base::partial; // <- look here
            *to_nxt++ = static_cast<uint8_t>(0xE0 |  (wc >> 12));
            *to_nxt++ = static_cast<uint8_t>(0x80 | ((wc & 0x0FC0) >> 6));
            *to_nxt++ = static_cast<uint8_t>(0x80 |  (wc & 0x003F));
        }

如果输出序列不能容纳因消耗一个输入宽字符而产生的多字节字符,则不会将任何内容写入输出序列。

于 2013-10-17T20:44:11.160 回答
2

尽管没有直接引用它,但我认为这是std::codecvt::out. 考虑以下场景:

  • std::codecvt::out以与您相同的方式使用 - 不将任何字符(可能不知道)翻译成您的out_buf.
  • 您现在想将另一个字符串翻译成您的out_buf(再次使用std::codecvt::out),以便它附加已经在里面的内容
  • 为此,您决定buf_mid在第一步翻译的字符串之后直接使用您所知道的指向。
  • 现在,如果std::codecvt::out按照您的期望工作(buf_mid指向第一个之后的字符),那么您的第一个字符out_buf将永远不会被写入,在这种情况下,这将不是您想要/期望的。

本质上,extern_type*& to_next( 的最后一个参数std::codecvt::out) 在这里作为您离开位置的参考 - 所以您知道从哪里继续 - 在您的情况下,这确实与您开始 ( extern_type* to) 参数的位置相同。

于 2013-10-17T18:55:11.787 回答