【问题标题】:ASCII and UTF-8 (or UCS-2 and UTF-16) strings in the same C++ project同一个 C++ 项目中的 ASCII 和 UTF-8(或 UCS-2 和 UTF-16)字符串
【发布时间】:2019-06-16 07:33:03
【问题描述】:

我们有一个项目,由于历史原因,字符串处理是编码和表示的杂音;我们肯定有一些地方只能可靠地处理 ASCII,一些地方可能使用 UTF-8,我怀疑外围的一些地方正在使用特定于平台的 8 位编码(当然在我们不同的目标平台之间有所不同),各种设计为采用 UCS-2 的地方,也许还有一些很乐意在 UTF-16 上运行的地方——所有这些地方有时都作为 C 风格的字符串(char*CHAR16*)传递,有时作为 C++ 字符串( std::stringstd::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”,事实上它实际上可以转换为拉丁语 1 而不是 ASCII。

【问题讨论】:

  • utf8_string s = {"foo"}; 可能是您的目标。换句话说,继续使用例如std::string 作为实现细节并将其包装在一个结构中。在顶部撒上一些重载的运算符,您应该能够逐步转换代码而不会出现太多问题。
  • 从不同的来源获取正确的编码是一项艰巨的工作。虽然你可以用 UTF8 很好地表示任何编码,但最后你应该坚持下去。

标签: c++ utf-8 ascii utf-16 ucs2


【解决方案1】:

我想我正在做一些事情,至少就 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>;

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

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

(有趣的事实:据我所知,如果using 子句被相应的typedefs 替换,这种机制甚至应该适用于良好的旧C++03。)

【讨论】:

    【解决方案2】:

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

    在内部仅使用一种数据类型(您的语言之一或在本例中类似的标准库)。

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

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

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

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

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

    【讨论】:

    • 您描述的几乎完全是我已经决定采用的一般路线,除了我打算使用强类型而不是名称前缀,以便编译器可以帮助我找到编码混合的地方up(我应该提到我认为我们在代码中确实存在此类问题)。
    • 您能详细说明一下注释方法吗?整个注释游戏对我来说是全新的。
    • @ChristophLipka:不,我无法详细说明。我对此知之甚少(但我知道它存在)。关于 gcc 扩展许可的讨论很多。但我从未涉足它。您可以在以下位置找到一些文档:lwn.net/Articles/689907 我假设它也在内核外部使用(并且我假设它可以用于您的情况)。
    • 强类型的问题:你丢失了库函数(因此很难实现和更改所有调用),或者它不够强(所以没用)。也许您可以为每种类型定义一个类,然后只存储字符串+实现强制转换函数。全部内联,因此不会丢失任何性能)。我的前缀更多的是没有标志日。
    • 粗略阅读来看,lwn.net 文章似乎与我的问题无关。它似乎在谈论 C 解析器库,而我需要 C++ 的解决方案。
    猜你喜欢
    • 2012-02-01
    • 2011-06-03
    • 2020-01-29
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    • 1970-01-01
    • 2010-10-16
    • 2011-09-06
    相关资源
    最近更新 更多