1

我们有一个项目,由于历史原因,字符串处理是编码和表示的杂音;我们肯定有一些地方只能可靠地处理 ASCII,一些地方可能使用 UTF-8,我怀疑外围的一些地方正在使用特定于平台的 8 位编码(当然在我们不同的目标平台之间有所不同),各种设计为采用 UCS-2 的地方,也许还有一些很乐意在 UTF-16 上运行的地方——所有这些地方有时都作为 C 风格的字符串 ( char*, CHAR16*) 传递,有时作为 C++ 字符串 ( std::string, std::basic_string<CHAR16>) 传递。当然,文档方面的内容很少。

作为解开这个混乱的第一步,我想建立一个类型系统,为不同的编码使用真正不同的类型。

我想到的一个想法是使用 egsigned char作为 ASCII 字符串和unsigned charUTF-8 字符串以及char16_tUCS-2 和shortUTF-16(或类似的东西)的基础,但这意味着我不会不能直接使用字符串文字。此外,能够简单地将 ASCII 字符串提供给期望 UTF-8 的函数(但反之亦然)会很整洁。

对于如何解决这个问题,或者甚至是工作代码,你有什么聪明的建议吗?

代码需要与 C++11 兼容。

请不要回答“始终始终使用 UTF-8”这样的答案,因为这几乎是我的最终目标;相反,这是关于创建一个我认为可以帮助我实现目标的工具。

-- 附录 --

我可能应该提到我认为我们已经遇到了字符串编码不能正确“排列”的问题,例如 UTF-16 字符串被传递给只能处理 UCS-2 字符串或特定于平台的 8 位字符串的函数被传递给需要 ASCII 字符串的函数。就在昨天,我发现专用的转换函数在其名称中带有“ASCII”,事实上它实际上会转换为/从 Latin-1 而不是 ASCII。

4

2 回答 2

1

我想我正在做一些事情,至少就 C++ 字符串 ( std::string, std::basic_string<chat16_t>) 而言;在那里,关键可能是使用非默认字符特征,如下所示:

using ASCII  = char;
using LATIN1 = char;
using UTF8   = char;
using UCS2   = char16_t;
using UTF16  = char16_t;

class ASCIICharTraits  : public std::char_traits<ASCII>  {};
class Latin1CharTraits : public std::char_traits<LATIN1> {};
class UTF8CharTraits   : public std::char_traits<UTF8>   {};
class UCS2CharTraits   : public std::char_traits<UCS2>   {};
class UTF16CharTraits  : public std::char_traits<UTF16>  {};

using ASCIIString  = std::basic_string<ASCII,  ASCIICharTraits>;
using Latin1String = std::basic_string<LATIN1, Latin1CharTraits>;
using UTF8String   = std::basic_string<UTF8,   UTF8CharTraits>;
using UCS2String   = std::basic_string<UCS2,   UCS2CharTraits>;
using UTF16String  = std::basic_string<UTF16,  UTF16CharTraits>;

使用不同类型作为模板的traits参数std::basic_string可确保编译器也将字符串类型视为不同类型,从而防止不兼容编码的 C++ 字符串的任何混淆,而无需编写包装框架。

请注意,要使其正常工作,需要对自定义特征类型进行子类化,而不是简单地使用别名。(理论上我可以从头开始编写新的 trait 类型,但派生于std::char_traits使工作更容易,并且应该确保我获得二进制兼容性,允许通过以下方式实现简单的转换(例如从 ASCII 到 Latin-1 或 UTF-8)一个简单的reinterpret_cast

(有趣的事实:据我所知,这个机制甚至应该适用于良好的旧 C++03,前提是using子句替换为相应typedef的 s。)

于 2019-01-24T16:21:29.170 回答
0

我推荐标准建议:三明治法。

在内部仅使用一种数据类型(您的语言之一或在这种情况下类似的标准库)。

仅在您将解码(输入)或编码(输出)的层上。还应该清楚为什么您决定使用一种编码。写入文件?UTF-8 很好(ASCII 是一个子集,所以保持为 UTF-8)。在这样的部分中,您还进行输入验证。应该是数字吗?检查它们是否是 unicode 数字。等。数据验证和编码(验证)应尽可能靠近读取输入。对于输出,采用相同的规则(但在这种情况下不应该进行验证)。

所以现在你可以给真正的字符串加上一些前缀(尝试一些独特的东西),并尝试找到你编码/解码的位置。尝试将这种编码移到外层。完成后,删除前缀。

您可以为其他编码使用其他前缀(只是暂时的)。同样在这种情况下尝试一些独特的东西。弄乱你的变量名,而不是类型。

作为替代方案,我认为您可以注释变量并使用外部工具来检查某些注释是否没有混合。Linux内核使用类似的东西(例如区分用户空间和内核指针)。我认为这对您的程序来说是一种矫枉过正。

为什么是三明治?现在您可能对 UTF-8、UCS-2、UTF-16 等有了很多了解。但这需要时间。下一位同事可能不知道所有这些细节,因此长期会引起问题。我们也使用整数,而不用担心它是一补码、二补码还是带符号位,但当我们写出数据时。对字符串做同样的事情。保持语义并忘记程序内部的编码。只有外层必须处理它。

于 2019-01-23T13:38:47.500 回答