繁体   English   中英

同一个 C++ 项目中的 ASCII 和 UTF-8(或 UCS-2 和 UTF-16)字符串

[英]ASCII and UTF-8 (or UCS-2 and UTF-16) strings in the same C++ project

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

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

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

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

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

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

——附录——

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

我想我正在做一些事情,至少就 C++ 字符串( std::stringstd::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>;

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

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

(有趣的事实:据我所知,这种机制甚至应该适用于旧的 C++03,只要using子句替换为相应的typedef 。)

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM