C++ 11中支持Unicode的程度如何?

Ral*_*zky 176 c++ unicode c++11

我读过并听说C++ 11支持Unicode.关于这一点的一些问题:

  • C++标准库如何支持Unicode?
  • 难道std::string做自己应该?
  • 我该如何使用它?
  • 潜在的问题在哪里?

R. *_*des 257

C++标准库如何支持unicode?

可怕.

快速扫描可能提供Unicode支持的库设施给我这个列表:

  • 字符串库
  • 本地化库
  • 输入/输出库
  • 正则表达式库

我认为除了第一个之外的所有人都提供了可怕的支持.在通过其他问题快速绕道之后,我会更详细地回到它.

难道std::string做自己应该?

是.根据C++标准,这是std::string它和它的兄弟姐妹应该做的:

类模板basic_string描述了可以存储由不同数量的任意类似char的对象组成的序列的对象,其中序列的第一个元素位于零.

那么,std::string那很好.这是否提供任何特定于Unicode的功能?没有.

应该是?可能不是.std::string很好,因为一系列的char对象.那很有用; 唯一令人烦恼的是它是一个非常低级的文本视图,标准C++不提供更高级别的文本.

我该如何使用它?

将它用作一系列char对象; 假装它是其他东西必然会痛苦地结束.

潜在的问题在哪里?

到处都是?让我们来看看...

字符串库

字符串库为我们提供了basic_string,它只是标准称为"类似char的对象"的序列.我把它们称为代码单元.如果您想要一个高级别的文本视图,这不是您想要的.这是适用于序列化/反序列化/存储的文本视图.

它还提供了C库中的一些工具,可用于弥合狭义世界和Unicode世界之间的差距:c16rtomb/ mbrtoc16c32rtomb/ mbrtoc32.

本地化库

本地化库仍然认为其中一个"类似char的对象"等于一个"字符".这当然是愚蠢的,并且使得除了像ASCII这样的一小部分Unicode之外,不可能使许多事情正常工作.

例如,考虑标准中<locale>标题中称为"便利接口"的内容:

template <class charT> bool isspace (charT c, const locale& loc);
template <class charT> bool isprint (charT c, const locale& loc);
template <class charT> bool iscntrl (charT c, const locale& loc);
// ...
template <class charT> charT toupper(charT c, const locale& loc);
template <class charT> charT tolower(charT c, const locale& loc);
// ...
Run Code Online (Sandbox Code Playgroud)

您如何期望任何这些功能正确分类,例如,U +1F34Cʙᴀɴᴀɴᴀ,如u8""u8"\U0001F34C"?它无法工作,因为这些函数只需要一个代码单元作为输入.

如果char32_t仅使用,则可以使用适当的区域设置:U'\U0001F34C'是UTF-32中的单个代码单元.

但是,这仍然意味着你只能使用toupper和得到简单的套管转换tolower,例如,对于某些德国语言环境来说,它们不够好:"ß"大写到"SS"☦但toupper只能返回一个字符代码单元.

接下来,wstring_convert/ wbuffer_convert和标准代码转换方面.

wstring_convert用于将一个给定编码中的字符串转换为另一个给定编码中的字符串.此转换涉及两种字符串类型,标准称为字节字符串和宽字符串.由于这些术语确实具有误导性,我更愿意分别使用"序列化"和"反序列化"而不是†.

要在其间转换的编码由作为模板类型参数传递的codecvt(代码转换构面)决定wstring_convert.

wbuffer_convert执行类似的功能,但作为一个宽的反序列化的流缓冲区,包装一个字节序列化的流缓冲区.任何I/O都是通过底层字节序列化流缓冲区执行的,其中包含与codecvt参数给出的编码之间的转换.将序列化写入该缓冲区,然后从中写入,并将读取读入缓冲区,然后从中进行反序列化.

本标准规定了一些的codecvt类模板与这些设施的使用:codecvt_utf8,codecvt_utf16,codecvt_utf8_utf16,和一些codecvt专业.这些标准方面共同提供以下所有转换.(注意:在下面的列表中,左侧的编码始终是序列化的字符串/ streambuf,右侧的编码始终是反序列化的字符串/ streambuf;标准允许在两个方向上进行转换).

  • UTF-8↔UCS-2用codecvt_utf8<char16_t>,以及codecvt_utf8<wchar_t>在哪里sizeof(wchar_t) == 2;
  • UTF-8↔UTF-32 codecvt_utf8<char32_t>,codecvt<char32_t, char, mbstate_t>以及codecvt_utf8<wchar_t>其中sizeof(wchar_t) == 4;
  • UTF-16↔UCS-2用codecvt_utf16<char16_t>,codecvt_utf16<wchar_t>哪里sizeof(wchar_t) == 2;
  • UTF-16↔UTF-32用codecvt_utf16<char32_t>,和codecvt_utf16<wchar_t>哪里sizeof(wchar_t) == 4;
  • UTF-8↔UTF-16 codecvt_utf8_utf16<char16_t>,codecvt<char16_t, char, mbstate_t>以及codecvt_utf8_utf16<wchar_t>其中sizeof(wchar_t) == 2;
  • 狭窄↔宽 codecvt<wchar_t, char_t, mbstate_t>
  • 无操作codecvt<char, char, mbstate_t>.

其中有几个很有用,但这里有很多尴尬的东西.

首先是圣洁的高代理人!这个命名方案很乱.

然后,有很多UCS-2支持.UCS-2是Unicode 1.0的编码,1996年被取代,因为它只支持基本的多语言平面.为什么委员会认为可以专注于20多年前被取代的编码,我不知道‡.它不像支持更多编码是坏事或任何事情,但UCS-2在这里经常出现.

我想说这char16_t显然是用于存储UTF-16代码单元.但是,这是标准的另一部分.codecvt_utf8<char16_t>与UTF-16无关.例如,wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")编译正常,但无条件失败:输入将被视为UCS-2字符串u"\xD83C\xDF4C",无法转换为UTF-8,因为UTF-8无法编码0xD800-0xDFFF范围内的任何值.

仍然在UCS-2前端,没有办法从这些方面读取UTF-16字节流到UTF-16字符串.如果您有一个UTF-16字节序列,则无法将其反序列化为字符串char16_t.这是令人惊讶的,因为它或多或少是一种身份转换.但更令人惊讶的是,支持将UTF-16流反序列化为UCS-2字符串codecvt_utf16<char16_t>,这实际上是一种有损转换.

但UTF-16-as-bytes支持相当不错:它支持从BOM检测endianess,或在代码中明确选择它.它还支持使用和不使用BOM生成输出.

没有一些更有趣的转换可能性.无法将UTF-16字节流或字符串反序列化为UTF-8字符串,因为从不支持UTF-8作为反序列化形式.

在这里,狭窄/广阔的世界与UTF/UCS世界完全分开.旧式窄/宽编码与任何Unicode编码之间没有转换.

输入/输出库

I/O库可用于使用上述wstring_convertwbuffer_convert设施以Unicode编码读取和写入文本.我不认为这部分标准库需要支持其他许多东西.

正则表达式库

我之前已经阐述了C++正则表达式和 Stack Overflow上的Unicode问题.我不会在这里重复所有这些要点,而只是声明C++正则表达式没有1级Unicode支持,这是使它们无法使用而不需要在任何地方使用UTF-32的最低要求.

而已?

对,就是那样.这是现有的功能.有许多Unicode功能无法像标准化或文本分段算法那样被看到.

U + 1F4A9.有没有办法在C++中获得更好的Unicode支持?

通常的嫌疑人:ICUBoost.Locale.


†不出所料,字节串是一串字节,即char对象.但是,与宽字符串文字(始终是wchar_t对象数组)不同,此上下文中的"宽字符串"不一定是wchar_t对象字符串.实际上,标准从未明确定义"宽字符串"的含义,因此我们只能从使用中猜测其含义.由于标准术语草率而且令人困惑,因此我以清晰的名义使用自己的术语.

像UTF-16这样的编码可以存储为序列char16_t,然后没有字节顺序; 或者它们可以存储为具有字节序的字节序列(每个连续的字节对可以char16_t根据字节顺序表示不同的值).该标准支持这两种形式.一系列对char16_t程序中的内部操作更有用.字节序列是与外部世界交换此类字符串的方式.因此,我将使用的术语而不是"字节"和"宽"是"序列化"和"反序列化".

‡如果您要说"但是Windows!" 抓住你的.自Windows 2000以来的所有Windows版本都使用UTF-16.

☦是的,我知道großesEszett(),但即使你要在一夜之间改变所有德国语言区域以使大写为ẞ,还有很多其他情况会失败.尝试大写U +FB00ʟᴀᴛɪɴsᴍᴀʟʟᴍᴀʟʟʟɪɢᴀᴛᴜʀᴇ.没有ʟᴀᴛɪɴᴄᴀᴘɪᴛᴀʟʟɪɢᴀᴛᴜʀᴇғғ; 它只是上升到两个Fs.或者U + 01F0ʟᴀᴛɪɴsᴍᴀʟʟʟᴇᴛᴛᴇʀᴊᴡɪᴛʜᴄᴀʀᴏɴ; 没有预先组合的资本; 它只是一个大写J和一个结合卡隆的大写.

  • 我读的越多,我就越觉得不理解这一切.几个月前我读了大部分这些东西,但仍觉得我再次发现了整件事......为了让我的可怜的大脑保持简单,现在有点痛,所有这些建议都在[utf8everywhere](http ://utf8everywhere.org/#how)仍然​​有效,对吗?如果我"只是"希望我的用户能够打开和写入文件而不管他们的系统设置我可以问他们文件名,将它存储在std :: string中,一切都应该正常工作,即使在Windows上也是如此?很抱歉(再次)...... (25认同)
  • @ graham.reeds哈哈,谢谢,但我知道这一点.检查"致谢"部分;) (19认同)
  • @Uflex你可以*真正*用std :: string做的就是把它当作二进制blob来对待.在正确的Unicode实现中,内部(因为它隐藏在实现细节深处)和外部编码都不重要(好吧,sorta,你仍然需要编码器/解码器可用). (5认同)
  • 也许@Uflex.我不知道你不理解的建议是否是一个好主意. (3认同)
  • ...无论你想如何存储它(但“wchar_t”将是愚蠢的)。当然,除非这是身份转换。这整件事让人感觉很恶心。无论是谁设计的,都不知道他们在做什么,委员会批准了它:( (2认同)

Mat*_* M. 39

标准库不支持Unicode(支持的任何合理含义).

std::string并不比std::vector<char>它更好:它完全没有注意到Unicode(或任何其他表示/编码),只是将其内容视为一个字节blob.

如果你只需要存储和连接blob,它的效果非常好; 但是只要你想要Unicode功能(代码点的数量,字母的数量......),你就不幸了.

我所知道的唯一一个综合图书馆是ICU.C++接口虽然源自Java,但它远非惯用语.

  • @Uflex:来自您链接的页面*为了实现这一目标,Boost.Locale使用最先进的Unicode和本地化库:ICU - Unicode的国际组件.* (11认同)
  • [Boost.Locale](http://www.boost.org/doc/libs/1_53_0/libs/locale/doc/html/index.html)怎么样? (2认同)

uck*_*man 23

由于Unicode NUL(U + 0000)是UTF-8中的空字节,并且这是null的唯一方法,因此您可以安全地将UTF-8存储在std::string(或在char[]或者char*)中.字节可以在UTF-8中出现.因此,您的UTF-8字符串将根据所有C和C++字符串函数正确终止,并且您可以使用C++ iostream(包括std::coutstd::cerr,只要您的语言环境是UTF-8)来使用它们.

你不能std::string为UTF-8做的就是获得代码点的长度.std::string::size()会告诉你字符串长度,以字节为单位,只等于你在UTF-8的ASCII子集中的代码点数.

如果你需要在代码点级别操作UTF-8字符串 - 不仅仅是存储和打印它们 - 或者如果你正在处理可能有许多内部空字节的UTF-16,你需要查看宽字符串类型.

  • 稍微不同的是`c_str()`确保结果后跟一个类似NUL char的对象,我不认为`data()`会这样做.不,看起来像`data()`现在也是这样.(当然,对于使用大小而不是从终结器搜索推断它的API来说,这不是必需的) (6认同)
  • 不再.`c_str()`现在只返回与`data()`相同的内容,即全部.采用大小的API可以使用它.没有的API,不能. (4认同)
  • `std :: string`可以抛入嵌入空值的iostream中就好了. (3认同)
  • 这是完全有意的.它根本不会破坏`c_str()`因为`size()`仍然有效.只有破坏的API(即那些无法像大多数C世界那样处理嵌入空值的API)才会中断. (3认同)
  • 嵌入的空值破坏了“c_str()”,因为“c_str()”应该将数据作为空终止的 C 字符串返回——这是不可能的,因为 C 字符串不能嵌入空值。 (2认同)

Som*_*ude 8

C++ 11 为Unicode 提供了一些新的文字字符串类型.

不幸的是,标准库中对非均匀编码(如UTF-8)的支持仍然很糟糕.例如,没有很好的方法来获取UTF-8字符串的长度(在代码点中).

  • 说实话,获取字符串代码点的长度并没有太多用途.例如,以字节为单位的长度可用于正确预分配缓冲区. (8认同)
  • @Uflex`std :: string`可以*保持*UTF-8字符串没有问题,但是例如`length`方法返回字符串中的字节数而不是代码点的数量. (7认同)
  • UTF-8字符串中的代码点数量不是一个非常有趣的数字:可以将`ñ`写成'带有TILDE的'LATIN SMALL LETTER N'(U + 00F1)(这是一个代码点)或'LATIN SMALL LETTER N'(U + 006E)后跟'COMBINING TILDE'(U + 0303),这是两个代码点. (2认同)

Jak*_*dle 5

然而,有一个非常有用的库,称为tiny-utf8,它基本上是/ 的直接替代品。它旨在填补仍然缺失的 utf8 字符串容器类的空白。std::stringstd::wstring

这可能是“处理” utf8 字符串的最舒适的方式(即,没有 unicode 规范化和类似的东西)。您可以轻松地对codepoints进行操作,而字符串仍以游程编码形式进行编码char