如何在跨平台(Windows,iOS,Android)C++应用程序中表示字符串?

Mar*_*cze 2 c++ windows directx android ios

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

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

值得注意的是,我在Windows中大量使用DirectWrite,其中API函数通常期望传递wchar_t*(顺便说一句,API文档声明"指向Unicode字符数组的指针.",我不是知道这是否意味着它们是否采用UTF-16编码)

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

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

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

(更新:删除了有关char*和std :: string差异的问题的部分.)

Mar*_*cze 6

我的问题的一部分来自我的误解,或者不完全理解字符串wstring类在C++中是如何工作的(我来自C#背景).在这个伟大的答案中描述了两者的区别以及利弊:std :: wstring VS std :: string.

string和wstring的工作原理

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

编码

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

std::string s("?");
Run Code Online (Sandbox Code Playgroud)

ű字符将通过在内存多于一个字节来表示,但无关与std :: string类,这是因为它可以编码字符串常量与UTF8编译器的功能(不是每个编译器虽然).(以L为前缀的字符串文字将由UTF16或UTF32中的wchar_t-s或其他内容表示,具体取决于编译器).
因此,字符串"ű"将在内存中用两个字节表示:0xC5 0xB1,并且std :: string类将不知道这两个字节在语义上是指UTF8中的一个字符(一个Unicode代码点),因此示例代码:

std::string s("?");
std::cout << s.length() << std::endl;
std::cout << s.substr(0, 1);
Run Code Online (Sandbox Code Playgroud)

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

2
?
Run Code Online (Sandbox Code Playgroud)

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

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

我的方法

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

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

// 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;
Run Code Online (Sandbox Code Playgroud)

字符串与上面的类一起存储,原始UTF16数据被传递到各种平台的特定API函数,目前所有这些函数似乎都支持UTF16编码.
实现可能不完美,但append,substr和size函数似乎正常工作.我仍然没有很多C++中的字符串处理经验,所以如果我说错了,请随意评论/编辑.