在C ++ 11中支持Unicode有多好?

我读过并且听说C ++ 11支持Unicode。 有几个问题:

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

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

可怕。

通过可能提供Unicode支持的图书馆设施的快速扫描给我这个列表:

  • string库
  • 本地化库
  • input/输出库
  • 正则expression式库

我觉得除了第一个之外,其他的都提供了可怕的支持。 通过其他问题快速绕道之后,我会再回到它的细节。

std::string应该做什么?

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

类模板basic_string描述的对象可以存储一个由不同数量的任意类字符对象组成的序列,序列中的第一个元素位置为零。

那么, std::string就好了。 这是否提供任何Unicode特定的function? 没有。

应该是? 可能不会。 作为一个char对象的序列, std::string很好。 这很有用; 唯一的烦恼是它是一个非常低层次的文本视图,标准的C ++不提供更高层次的视图。

我如何使用它?

用它作为一个char对象的序列; 假装它是别的东西必然会以痛苦结束。

潜在的问题在哪里?

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

string库

string库为我们提供了basic_string ,它只是标准调用“类字符对象”的序列。 我称之为代码单元。 如果你想要一个高级的文本视图,这不是你在找什么。 这是适合序列化/反序列化/存储的文本视图。

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

本地化库

本地化图书馆仍然认为其中一个“类字符对象”等于一个“字符”。 这当然是愚蠢的,并且使得除了像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); // ... 

你如何期望这些function中的任何一个正确地分类,比如说U + 1F34C?,如u8"🍌"u8"\U0001F34C" ? 这是不可能的,因为这些函数只用一个代码单元作为input。

如果仅使用char32_t则可以使用适当的语言环境: U'\U0001F34C'是UTF-32中的单个代码单元。

然而,这仍然意味着你只能通过touppertolower获得简单的shell转换,例如,对于某些德语语言环境来说,它不够好:“ß”大写字母转换为“SS”,但toupper只能返回一个字符代码单元。

接下来, wstring_convert / wbuffer_convert和标准代码转换构面。

wstring_convert用于将给定编码中的string转换为另一种给定编码中的string。 在这个转换中涉及到两个stringtypes,标准调用一个字节string和一个宽string。 由于这些术语确实是误导性的,我宁愿分别使用“序列化”和“反序列化”来代替

转换的编码由作为模板typesparameter passing给wstring_convert的codecvt(代码转换构面) wstring_convert

wbuffer_convert执行类似的function,但作为一个宽的反序列化的stream缓冲区包装一个字节序列化的stream缓冲区。 任何I / O都是通过底层的字节序列化的stream缓冲区执行的,并且与codecvt参数给出的编码进行转换。 写入序列化到该缓冲区,然后写入,读取读取缓冲区,然后反序列化。

该标准提供了一些codecvt类模板以供这些工具使用: codecvt_utf8codecvt_utf16codecvt_utf8_utf16和一些codecvt专业化。 这些标准方面一起提供所有以下转换。 (注意:在下面的列表中,左边的编码总是串行化的string/stream缓冲区,而右边的编码总是反序列化的string/stream缓冲区;标准允许双向转换)。

  • UTF- codecvt_utf8<char16_t> -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- codecvt_utf16<char16_t> -2, codecvt_utf16<char16_t>codecvt_utf16<wchar_t> ,其中sizeof(wchar_t) == 2 ;
  • UTF- codecvt_utf16<char32_t>使用codecvt_utf16<char32_t>codecvt_utf16<wchar_t> UTF-32,其中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>缩小范围
  • no-op与codecvt<char, char, mbstate_t>

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

第一个非圣洁的高代理! 该命名scheme是混乱的。

然后,有很多UCS-2的支持。 UCS-2是Unicode 1.0的编码,1996年被取代,因为它只支持基本的多语种平面。 为什么委员会认为需要关注20年前被取代的编码,我不知道和ddagger; 。 这不是像支持更多的编码是坏的或什么,但UCS-2在这里显示太频繁。

我会说, char16_t显然意味着存储UTF-16代码单元。 但是,这是否认的标准的一部分。 codecvt_utf8<char16_t>与UTF-16无关。 例如, wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")将会正常编译,但是会无条件地失败:input将被视为UCS-2stringu"\xD83C\xDF4C" ,无法转换为UTF-8,因为UTF-8无法编码0xD800-0xDFFF范围内的任何值。

仍然在UCS-2前端,没有办法从UTF-16stream中读取这些方面的UTF-16string。 例如,如果您有一个UTF-16字节序列,则不能将其反序列化为char16_tstring。 这是令人惊讶的,因为它或多或less是一种身份转换。 但更令人惊奇的是,支持将UTF-16stream反序列化为带有codecvt_utf16<char16_t>的UCS-2string,这实际上是一种有损转换。

但是UTF-16-as-bytes支持非常好,它支持从BOM中检测字节数,或者在代码中明确地select字节数。 它也支持产生有和没有BOM的输出。

有一些更有趣的转换可能性缺席。 从UTF-16stream或string反序列化为UTF-8string是没有办法的,因为反序列化的forms从来不支持UTF-8。

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

input/输出库

I / O库可用于使用上述的wstring_convertwbuffer_convert工具以Unicode编码读写文本。 我不认为还有其他的东西需要这个标准库的这个部分来支持。

正则expression式库

我已经阐述了C ++正则expression式和Unicode堆栈溢出之前的问题。 我不会在这里重复所有这些要点,只是指出C ++正则expression式没有1级的Unicode支持,这是最低限度的使它们可用而不需要在任何地方使用UTF-32。

而已?

对,就是那样。 这是现有的function。 有很多的Unicodefunction,无处可见像标准化或文本分割algorithm。

U + 1F4A9 。 有没有办法获得一些更好的Unicode支持在C + +?

通常的嫌疑犯: ICU和Boost.Locale 。


不出所料 ,字节串是一串字节,也就是char对象。 但是,与宽string文字 (始终是wchar_t对象数组)不同,此上下文中的“宽string”不一定是wchar_t对象的string。 事实上,这个标准从来没有明确地定义“宽string”是什么意思,所以我们只能猜测使用的含义。 由于标准术语是草率和混乱的,我用清晰的名义使用自己的术语。

像UTF-16这样的编码可以被存储为char16_t序列,然后没有endianness; 或者可以将它们存储为具有字节序的字节序列(每个连续的一对字节可以表示不同的char16_t值,具体取决于字节序)。 标准支持这两种forms。 char16_t的序列对于程序中的内部操作更有用。 字节序列是与外部世界交换这样的string的方式。 因此,我将使用的术语而不是“字节”和“宽”是“序列化”和“反序列化”。

&ddagger; 如果你打算说“但是Windows!” 抱着你的</ s> 。 自Windows 2000以来,所有版本的Windows都使用UTF-16。

标准库不支持Unicode(任何合理的支持意义)。

std::string并不比std::vector<char>更好:它完全忽略了Unicode(或任何其他表示/编码),并简单地将其内容视为一个字节块。

如果你只需要存储和链接斑点,它工作得很好, 但只要你希望Unicodefunction(代码点的数量,字形的数量,…)你运气不好。

我知道的唯一综合图书馆是ICU。 C ++接口虽然是从Java的派生的,所以远不是惯用的。

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

用UTF-8的std::string无法做到的是获得代码点的长度。 std::string::size()会告诉你string的长度(以字节为单位) ,这只与UTF-8的ASCII子集内的代码点数相等。

如果您需要在代码点级别对UTF-8string进行操作,而不仅仅是存储和打印它们,或者如果要处理的UTF-16可能具有许多内部空字节,则需要查看宽字符的stringtypes。

C ++ 11为Unicode提供了一些新的文字stringtypes 。

不幸的是,标准库中对非统一编码(如UTF-8)的支持仍然不好。 例如,没有很好的方法来获得UTF-8string的长度(代码点)。

但是,有一个非常有用的库叫做tiny-utf8 ,它基本上是std::string / std::wstring一个替代品 。 它旨在填补仍然缺失的utf8-string容器类的缺口。

这可能是用utf8string“处理”的最舒服的方式(也就是说,没有unicode标准化和类似的东西)。 您可以舒适地操作代码点 ,而您的string仍然以运行长度编码的char编码。