Unicode在c++ 11中的支持程度

How well is Unicode supported in C++11?

本文关键字:支持 程度 c++ Unicode      更新时间:2023-10-16

我听说c++ 11支持Unicode。有几个问题:

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

c++标准库对unicode的支持程度如何?

非常。快速浏览一下可能提供Unicode支持的库功能,我得到了这个列表:

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

我认为除了第一个之外,其他的都提供了糟糕的支持。在快速浏览完你的其他问题后,我将会更详细地讨论它。

std::string做它应该做的吗?

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

类模板basic_string描述了可以存储由任意数量的类似char的对象组成的序列,序列的第一个元素位于位置0。

好吧,std::string做得很好。它是否提供了任何特定于unicode的功能?没有。

应该吗?可能不会。std::string可以作为char对象的序列。这是有用的;唯一的烦恼是它是一个非常低级的文本视图,而标准c++没有提供一个高级视图。

如何使用?

使用它作为char对象的序列;假装是别的东西注定会以痛苦告终。

潜在的问题在哪里?

到处都是?让我们看看…

<字符串库/em>

string标准库提供了basic_string,它仅仅是标准所称的"类字符对象"的一个序列。我称它们为代码单元。如果您想要文本的高级视图,这不是您要找的。这是一个适合序列化/反序列化/存储的文本视图。

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

<本地化库/em>

本地化库仍然认为这些"类字符对象"中的一个等于一个"字符"。这当然是愚蠢的,并且使得除了Unicode的一些小子集(如ASCII)之外的许多事情无法正常工作。

考虑一下,例如,标准在<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);
// ...

你如何期望这些函数中的任何一个能够正确地分类,比如,在u8" "u8"U0001F34C"中,U+1F34C 它不可能工作,因为这些函数只接受一个代码单元作为输入。

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

然而,这仍然意味着您只能使用touppertolower获得简单的大小写转换,例如,对于一些德语区域设置来说,这还不够好:"ß"大写为"SS";但是toupper只能返回一个字符代码单元。

接下来是wstring_convert/wbuffer_convert和标准代码转换方面。

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

要转换的编码由作为模板类型参数传递给wstring_convert的codecvt(代码转换facet)决定。

wbuffer_convert执行类似的功能,但作为反序列化流缓冲区,封装了字节序列化流缓冲区。任何I/O都通过底层的byte序列化流缓冲区执行,并与codecvt参数给出的编码进行转换。

写入序列化到该缓冲区,然后从它写入,读取读取缓冲区,然后从它反序列化。标准提供了一些编码类模板用于这些工具:codecvt_utf8codecvt_utf16codecvt_utf8_utf16和一些codecvt专门化。这些标准方面一起提供了以下所有转换。(注意:在下面的列表中,左边的编码总是序列化的string/streambuf,右边的编码总是反序列化的string/streambuf;标准允许两个方向的转换)。

  • UTF-8↔UCS-2 withcodecvt_utf8<char16_t>, andcodecvt_utf8<wchar_t>wheresizeof(wchar_t) == 2;
  • UTF-8↔codecvt_utf8<char32_t>,codecvt<char32_t, char, mbstate_t>,codecvt_utf8<wchar_t>wheresizeof(wchar_t) == 4;
  • UTF-16↔UCS-2 withcodecvt_utf16<char16_t>codecvt_utf16<wchar_t>wheresizeof(wchar_t) == 2;
  • UTF-16↔codecvt_utf16<char32_t>codecvt_utf16<wchar_t>wheresizeof(wchar_t) == 4;
  • UTF-8↔codecvt_utf8_utf16<char16_t>,codecvt<char16_t, char, mbstate_t>,codecvt_utf8_utf16<wchar_t>wheresizeof(wchar_t) == 2;
  • 窄↔宽↔codecvt<wchar_t, char_t, mbstate_t>
  • no-op withcodecvt<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"xD83CxDF4C",它不能转换为UTF-8,因为UTF-8不能编码0xD800-0xDFFF范围内的任何值。

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

对UTF-16-as-bytes的支持非常好:它支持从BOM中检测尾序,或者在代码中显式地选择它。它还支持生成带有或不带有BOM的输出。

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

这里的窄/宽世界与UTF/UCS世界是完全分开的。旧式窄/宽编码和任何Unicode编码之间没有转换。

输入/输出库

I/O库可以使用上面描述的wstring_convertwbuffer_convert设施来读写Unicode编码的文本。我不认为标准库的这一部分还需要支持什么。

正则表达式库

我以前已经阐述过c++正则表达式和Unicode在Stack Overflow上的问题。我不会在这里重复所有这些要点,但只是声明c++正则表达式不具有1级Unicode支持,这是使它们可用而不诉诸于到处使用UTF-32的最低要求。

吗?

是的,就是这样。这是现有的功能。有很多Unicode功能,像标准化或文本分割算法是看不到的。

U + 1 f4a9。有没有办法在c++中获得更好的Unicode支持?

常用的:ICU和Boost.Locale.


匕首;毫无疑问,字节串是字节串,即char对象。然而,与宽字符串字面值不同,总是wchar_t对象的数组,在此上下文中的"宽字符串"不一定是wchar_t对象的字符串。事实上,标准从未明确定义"宽字符串"的含义,所以我们只能从用法中猜测其含义。由于标准术语是草率和令人困惑的,为了清晰起见,我使用我自己的术语。

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

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

☦是的,我知道großes Eszett(ẞ),但即使你在一夜之间改变所有德国地区ßẞ大写,还有很多其他的情况下,这将失败。试试大写的U+FB00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _没有任何一种说法是:ғғ;只是大写到两个f。或U+01F0;没有预先设定好的资本;它只是大写字母J和一个组合号。

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

std::string并不比std::vector<char>好:它完全无视Unicode(或任何其他表示/编码),只是将其内容视为字节的blob

如果你只需要存储和连接blobs,它工作得很好;但是一旦你想要Unicode功能(代码点的数量字素的数量等),你就不走运了。

我所知道的唯一全面的库是ICU。c++接口是从Java接口派生出来的,所以它远不是惯用的。

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

对于UTF-8,std::string不能做的是获取代码点长度。std::string::size()将以字节为单位告诉您字符串长度,它只等于在UTF-8的ASCII子集内的代码点数。

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

c++ 11为Unicode提供了两个新的字面值字符串类型。

不幸的是,标准库中对非统一编码(如UTF-8)的支持仍然很差。例如,没有很好的方法来获取UTF-8字符串的长度(以码位为单位)。

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

这可能是'处理' utf8字符串的最舒适的方式(也就是说,没有unicode规范化和类似的东西)。您可以轻松地操作代码点,而您的字符串保持在运行长度编码的chars中进行编码。