C++和UTF8 - 为什么不能只使用ASCII替换?

10
在我的应用程序中,由于使用不同的API(boost、win32、ffmpeg等),我必须不断地在std::stringstd::wstring之间转换字符串。特别是对于ffmpeg,字符串最终会变成utf8->utf16->utf8->utf16,仅仅是为了打开一个文件。
由于UTF8向后兼容ASCII,因此我认为我可以始终将所有字符串一致地存储为UTF-8 std::string,只有在调用某些不寻常的函数时才转换为std::wstring
这种方法效果还不错,我实现了utf8的to_lower、to_upper、iequals。但是,我遇到了几个死胡同,如std::regex和普通字符串比较。要使其可用,我需要基于std::string实现自定义的ustring类,并重新实现所有相应的算法(包括正则表达式)。
基本上,我的结论是utf8不太适合一般用途。而当前的std::string/std::wstring也很混乱。
然而,我的问题是为什么默认的std::string""没有简单地改为使用UTF8?特别是UTF8向后兼容?可能有一些编译器标志可以做到这一点吗?当然,STL实现需要自动适应。
我看过ICU,但它与假定basic_string的API不太兼容,例如没有begin/end/c_str等函数。

4
这将破坏兼容性,因为数百万已存在的程序假定8位编码不同于UTF-8。一些旧程序假设8位编码是EBCDIC(甚至不与ASCII兼容)。更常见的是,程序会认为它是437代码页。在非英语国家,通常假设是GB2312、Big5、ShiftJIS、ISCII、1256或其他为特定语言定制的编码。 - Raymond Chen
@Simon:将输入的std::string简单转换为std::wstring,运行to_upper/lower,然后再转换回std::string的琐碎解决方案很难出错。 - ronag
1
@SimonRichter:区域设置类也不行。它们基于每个字符,而某些语言(如希腊语)具有非双射映射,因此 to_lower 是上下文相关的... - Matthieu M.
1
这个问题的问题在于std :: string""可以并且经常是UTF-8。也许提问者想要语言来指定它,并且真正想知道为什么没有这样做。 - bames53
2
你的前提是错误的。"" 字符串字面值的值由执行字符集确定,而 u8"" 字面值的值由UTF-8确定。这些是两个不同、不相交、不相关的问题领域。 - Kerrek SB
显示剩余5条评论
3个回答

8
主要问题在于内存表示和编码的混淆。
在 Unicode 编码中,没有一个真正适合文本处理的编码。一般用户关心的是字形(屏幕上显示的内容),而编码是以代码点为定义的... 有些字形由多个代码点组成。
因此,当我们询问“Hélène”(法语名字)的第五个字符时,这个问题就变得非常混乱:
- 从字形角度来看,答案是 n。 - 从代码点的角度来看,这取决于é和è的表示方式(它们可以用一个代码点或使用变音符号表示成一对...)
根据问题的来源(面前的终端用户还是编码例程),答案完全不同。
因此,我认为真正的问题是:为什么我们在这里谈论编码?
今天这已经没有任何意义了,我们需要两个“视图”:字形和代码点。
不幸的是,std::string 和 std::wstring 接口是从人们认为 ASCII 足够的时代继承下来的,而所做的进展并没有真正解决这个问题。
我甚至不明白为什么需要指定内存表示,这是实现细节。用户只需要做到:
- 在 UTF-* 和 ASCII 中读写 - 在字形上工作 - 编辑字形(管理变音符号)
...谁在意它是如何表示的呢?我以为好的软件建立在封装上?
好吧,C 在意,我们需要互操作性... 所以我猜当 C 修复这个问题时,问题也会解决。

我想有些用户确实关心他们字符串的字节是什么。并非每个人都想迭代图形元素。有些人想迭代代码点。有些人想要实际的代码单元。 - Nicol Bolas
@NicolBolas:我理解需要处理代码点或字形,但我认为编码应该仅限于与外部世界的接口。这就像直接处理JSON或XML字符串一样痛苦。 - Matthieu M.

3

您无法将代码页设置为UTF-8,这主要是因为Microsoft的原因。他们决定不支持Unicode作为UTF-8,因此在Windows下对UTF-8的支持非常有限。

在Windows下,您不能使用UTF-8作为代码页,但您可以进行UTF-8的转换。


2
是的。顺便说一句,在Linux中,对UTF-8的支持是透明的。我甚至在源代码中使用像µs这样的字符串,一切都运行得很好。 - user405725
4
当微软让Windows NT支持Unicode时,UTF-8还不存在。 - Roger Lipscombe
1
@MartinBeckett:strlen()函数计算的是字符串中char元素的数量,而不是字符的数量。在UTF-8编码的字符串(或任何其他Ansi字符集中),每个char表示一个编码单元。你可以在UTF-8字符串中使用strlen()函数,但要知道它将计算UTF-8编码单元的数量,而不是字符的数量。同样的情况也适用于UTF-16,因为它只是另一种Unicode编码。在UTF-16编码的字符串中,每个wchar_t表示一个编码单元。要计算实际字符的数量,在任何编码中,你必须先将字符串解码为UTF-32。 - Remy Lebeau
1
@MartinBeckett:在UTF-16和UTF-8中确定屏幕上字符串的长度并不比后者更微不足道。它们只是相同Unicode数据的不同编码方式,而决定屏幕显示效果的是Unicode数据以及字体对其的解释。无论哪种编码方式,都必须进行解码。 - Remy Lebeau
2
@MartinBeckett:是的,Windows确实使用UTF-16,自Windows 2000以来就一直如此(MSDN也这么说)。单个wchar_t只能在BMP内部容纳一个UTF-16/UCS2代码单元,但是多个wchar_t组成的字符串可以容纳完全编码的UTF-16代码单元和代理对,从而可以编码整个Unicode字符集。Win32 API支持并期望使用这种方式。 - Remy Lebeau
显示剩余4条评论

3
在Windows上使用UTF8有两个问题。
1. 您无法确定一个字符串将占用多少字节 - 这取决于哪些字符存在,因为某些字符需要1个字节,某些字符需要2个字节,某些字符需要3个字节,而某些字符则需要4个字节。
2. Windows API使用UTF16。由于大多数Windows程序会频繁调用Windows API,因此在转换时存在相当大的开销。注意,您可以进行“非Unicode”构建,看起来像是使用utf8的Windows API,但实际上每次调用都会进行反向转换。
UTF16的主要问题在于字符串的二进制表示取决于程序运行的特定硬件上字中的字节顺序。这在大多数情况下并不重要,除非在计算机之间传输字符串时不能确定另一台计算机是否使用相同的字节顺序。
那么该怎么办呢?我在所有程序“内部”都使用UTF16。当必须将字符串数据存储在文件中或从套接字传输时,我首先将其转换为UTF8。
这意味着我的95%代码简单高效,并且所有混乱的UTF8和UTF16之间的转换都可以隔离到负责I/O的程序中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接