2

我正在开发一个应用程序,其核心代码库将是 Windows、iOS 和 Android 的跨平台。

我的问题是:我应该如何在内部表示这个应用程序使用的字符串,以便能够在所有三个平台上有效地使用它们?

需要注意的是,我在 Windows 中大量使用 DirectWrite,其中 API 函数通常期望 wchar_t* 被传递(顺便说一句。API 文档指出“指向 Unicode 字符数组的指针。”,我没有知道这是否意味着它们采用 UTF-16 编码)

我看到了三种不同的方法(但是我发现很难掌握以跨平台方式使用 C++ 处理 Unicode 字符串的细节,所以我可能错过了一些重要的概念):

  • 在内部各处使用 std::string (并以 UTF-8 编码存储字符串?),并将它们转换为 DirectWrite API 所需的 wchar_t* (我不知道文本处理 API 需要什么Android 和 iOS 尚未)。
  • 在内部到处使用 std::wstring 。如果我理解正确,从内存使用的角度来看,这不会有效,因为 wchar_t 在 iOS 和 Android 上是 4 个字节(这是否意味着我必须在 Windows 上以 UTF-16 和Android/iOS 上的 UTF-32?)
  • 使用抽象基类为字符串创建抽象,并专门为不同平台实现内部存储。

最好的解决方案是什么?顺便说一句,是否有任何现有的跨平台库可以抽象字符串处理?(还有,读取和序列化 Unicode 字符串)

(更新:删除了关于 char* 和 std::string 区别的问题的部分。)

4

3 回答 3

6

我的问题的一部分来自我的误解,或者不完全理解stringwstring类在 C++ 中是如何工作的(我来自 C# 背景)。这个很好的答案已经描述了两者的区别和利弊:std::wstring VS std::string

string 和 wstring 的工作原理

对我来说,关于 string 和 wstring 类的重要的发现是它们在语义上并不代表一段编码文本,而只是一个 char 或 wchar_t 的“字符串”。它们更像是一个简单的数据数组,带有一些特定于字符串的操作(如 append 和 substr),而不是表示文本。他们都不知道任何类型的字符串编码,他们将每个 char 或 wchar_t 元素单独处理为单独的字符。

编码

但是,在大多数系统上,如果您从带有特殊字符的字符串文字创建字符串,如下所示:

std::string s("ű");

ű字符将由内存中的一个以上字节表示,但这与 std::string 类无关,这是编译器的一个特性,因为它可以使用 UTF8 编码字符串文字(虽然不是每个编译器)。(并且以 L 为前缀的字符串文字将由 wchar_t-s 以 UTF16 或 UTF32 或其他方式表示,具体取决于编译器)。
因此字符串“ű”将在内存中用两个字节表示:0xC5 0xB1,并且 std::string 类不会知道这两个字节在语义上意味着 UTF8 中的一个字符(一个 Unicode 代码点),因此示例代码:

std::string s("ű");
std::cout << s.length() << std::endl;
std::cout << s.substr(0, 1);

产生以下结果(取决于编译器,一些编译器不将字符串文字视为 UTF8,而一些编译器依赖于源文件的编码):

2
�

size() 函数返回 2,因为 std::string 唯一知道的是它存储了两个字节(两个字符)。并且 substr 也“原始地”工作,它返回一个包含单个字符0xC5的字符串,它显示为 �,因为它不是有效的 UTF8 字符(但这不会打扰 std::string)。

从中我们可以看出,处理编码的是平台的各种文本处理 API,比如简单的coutDirectWrite

我的方法

在我的应用程序中,DirectWrite 非常重要,它只接受以 UTF16 编码的字符串(以 wchar_t* 指针的形式)。所以我决定将字符串存储在内存和以 UTF16 编码的文件中。但是,我想要一个跨平台的实现,它可以在 Windows、Android 和 iOS 上处理 UTF16 字符串,而std::wstring无法做到这一点,因为它的数据大小(以及它适合使用的编码)是平台相关的。

为了创建一个跨平台、严格的 UTF16 字符串类,我将basic_string模板化为 2 字节长的数据类型。非常令人惊讶 - 至少对我来说 - 我几乎没有在网上找到关于此的信息,我的解决方案基于这种方法。这是代码:

// Define this on every platform to be 16 bytes!
typedef unsigned short char16;

struct char16_traits
{
    typedef char16 _E;
    typedef _E char_type;
    typedef int int_type;
    typedef std::streampos pos_type;
    typedef std::streamoff off_type;
    typedef std::mbstate_t state_type;
    static void assign(_E& _X, const _E& _Y)
    {_X = _Y; }
    static bool eq(const _E& _X, const _E& _Y)
    {return (_X == _Y); }
    static bool lt(const _E& _X, const _E& _Y)
    {return (_X < _Y); }
    static int compare(const _E *_U, const _E *_V, size_t _N)
    {return (memcmp(_U, _V, _N * 2)); }
    static size_t length(const _E *_U)
    {
        size_t count = 0;
        while(_U[count] != 0)
        {
            count++;
        }
        return count;
    }
    static _E * copy(_E *_U, const _E *_V, size_t _N)
    {return ((_E *)memcpy(_U, _V, _N * 2)); }
    static const _E * find(const _E *_U, size_t _N, const _E& _C)
    {
        for(int i = 0; i < _N; ++i) {
            if(_U[i] == _C) {
                return &_U[i];
            }
        }
        return 0;
    }
    static _E * move(_E *_U, const _E *_V, size_t _N)
    {return ((_E *)memmove(_U, _V, _N * 2)); }
    static _E * assign(_E *_U, size_t _N, const _E& _C)
    {
        for(size_t i = 0; i < _N; ++i) {
            assign(_U[i], _C);
        }
        return _U;
    }
    static _E to_char_type(const int_type& _C)
    {return ((_E)_C); }
    static int_type to_int_type(const _E& _C)
    {return ((int_type)(_C)); }
    static bool eq_int_type(const int_type& _X, const int_type& _Y)
    {return (_X == _Y); }
    static int_type eof()
    {return (EOF); }
    static int_type not_eof(const int_type& _C)
    {return (_C != eof() ? _C : !eof()); }
};

typedef std::basic_string<unsigned short, char16_traits> utf16string;

字符串用上面的类存储,原始的 UTF16 数据被传递给各个平台的特定 API 函数,目前所有这些似乎都支持 UTF16 编码。
实现可能并不完美,但是 append、substr 和 size 函数似乎可以正常工作。我在 C++ 中的字符串处理方面仍然没有太多经验,所以如果我陈述不正确,请随时评论/编辑。

于 2012-07-19T20:34:54.023 回答
2

std::strings 和 char* 之间的区别在于,std::string 类使用 C++ 特性而 char* 不使用。std::string 是 chars 的容器类,并定义了使用它的便捷方法, char* 是指向您可能使用的某些内存的指针。

如果您正在寻找一些独立于平台的基类,我会指向您QString。这是 Qt 库的一部分,旨在实现 C++ 的平台独立实现。它也是 OpenSource,因此您可以使用它来了解其他人如何实现平台独立字符串。文档也很好

于 2012-07-16T11:38:01.957 回答
1

在每个平台上实现一个抽象类以不同的方式表示似乎是个坏主意。额外的工作实现和测试(在每个平台上),并且会比仅仅使用 std::wstring 增加更多的开销(当然你可以通过不使用抽象类来抵消开销,而是使用 #ifdefs 来切换实现,但仍然是额外的工作)。

在任何地方使用 std::string 或 std::wstring 似乎都是可行的方法,实现一些实用函数将您选择的字符串转换为系统相关格式,您不会有问题。我正在开发一个已经在 iOS、Windows、Linux 和 Mac 上运行的多平台项目,在这个项目中我使用了多字节 std::string 并且没有太多问题,从未使用过 std::wstring 但我没有不明白为什么它不起作用。

于 2012-07-16T12:08:01.733 回答