我们有一个项目,由于历史原因,字符串处理是编码和表示形式的杂音;我们肯定有一些地方只能可靠地处理ASCII,有些地方可能使用UTF-8,外围一些地方我怀疑使用的是平台特定的8位编码(当然,在我们不同的目标平台之间会有所不同),设计用于UCS-2的地方,也许还有一些愿意在UTF-16上运行的地方-所有这些有时都作为C样式字符串(char*
,CHAR16*
)传递,有时作为C ++字符串(std::string
,std::basic_string<CHAR16>
)。当然,在文档方面很少。
作为弄清这种混乱的第一步,我想建立一个类型系统,对不同的编码使用真正不同的类型。
一个想到的想法是使用例如signed char
作为ASCII字符串的基础,unsigned char
作为UTF-8字符串,以及char16_t
对于UCS-2和short
作为UTF-16(或类似的东西)行),但这意味着我将无法直接使用字符串文字。而且,能够将ASCII字符串简单地馈送到需要UTF-8的函数(反之亦然)也很简单。
您对此有什么明智的建议,甚至可以编写代码吗?
该代码必须与C ++ 11兼容。
请不要回答“在整个过程中始终使用UTF-8”这样的答案,因为无论如何这都是我的最终目标。相反,这是关于创建一个我认为对我有很大帮助的工具。
-附录-
我可能应该提到我认为我们已经遇到了字符串编码不能正确“对齐”的问题,例如将UTF-16字符串传递给只能处理UCS-2字符串的函数,或者将平台特定的8位字符串传递给需要ASCII字符串的函数。就在昨天,我发现专用转换函数的名称中带有“ ASCII”,事实上,它们实际上会转换为Latin-1或从Latin-1转换为ASCII。
答案 0 :(得分: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 ++字符串的混淆,而无需编写包装器框架。
请注意,要实现此目的,需要对自定义特征类型进行子类化,而不仅仅是别名。 (从理论上讲,我可以从头开始编写新的特征类型,但是从std::char_traits
派生则使这项工作变得更加容易,并且应确保我获得了二进制兼容性,从而实现了微不足道的转换(例如从ASCII到Latin-1或UTF的转换) -8)通过简单的reinterpret_cast
。
(有趣的事实:据我所知,只要将using
子句替换为相应的typedef
,该机制甚至可以与旧的C ++ 03一起工作。)
答案 1 :(得分:0)
我推荐标准建议:三明治法。
内部仅使用一种数据类型(一种或多种语言,在这种情况下为标准库)。
仅在层上可以解码(输入)或编码(输出)。还应该清楚为什么您决定一种编码。写文件? UTF-8很好(ASCII是一个子集,因此将其保留为UTF-8)。在这一部分中,您还将进行输入验证。应该是数字吗?检查它们是否为unicode号。等等。数据验证和编码(验证)应尽可能靠近读取输入。对于输出,请遵循相同的规则(但在这种情况下,不应进行验证)。
因此,现在您可以在真正的字符串前加上一些前缀(尝试一些独特的方法),然后尝试查找编码/解码的位置。尝试在外层移动此类编码。完成后,删除前缀。
您可以对其他编码使用其他前缀(只是暂时的)。同样在这种情况下,请尝试一些独特的东西。麻烦您的变量名,而不是类型。
作为替代方案,我认为您可以注释变量并使用外部工具检查某些注释是否混合在一起。 Linux内核使用类似的东西(例如,区分用户空间和内核指针)。我认为这对您的程序来说太过分了。
为什么要三明治?现在您可能对UTF-8,UCS-2,UTF-16等了解得更多。但这花了一些时间。下一位同事可能不知道所有这些详细信息,因此从长远来看会引起问题。我们也使用整数,而不必担心它是单补码,二补码还是带符号位,但是在写数据时就不用担心。对字符串执行相同的操作。保留语义,忘记程序内部的编码。只有外层必须处理它。