2

私は、コア コード ベースが Windows、iOS、Android のクロスプラットフォームであるアプリケーションを開発しています。

私の質問は、このアプリで使用される文字列を 3 つのプラットフォームすべてで効果的に使用できるようにするには、内部的にどのように表現すればよいですか?

私が Windows で DirectWrite を多用していることに注意することが重要です。その API 関数は通常、wchar_t* が渡されることを期待しています (ところで、API ドキュメントには、「Unicode 文字の配列へのポインター」と記載されています。これが UTF-16 エンコーディングであることを意味するかどうかを知っています)

3 つの異なるアプローチが見られます (ただし、クロスプラットフォームの方法で C++ を使用して Unicode 文字列を処理する詳細を把握するのは非常に難しいため、いくつかの重要な概念を見落としている可能性があります)。

  • どこでも std::string を内部的に使用し (そして文字列を UTF-8 エンコーディングで保存しますか?)、それらを DirectWrite API に必要な場所で wchar_t* に変換します ( Android と iOS はまだ)。
  • どこでも内部的に std::wstring を使用します。私が正しく理解している場合、これはメモリ使用量の観点からは効果的ではありません。なぜなら、wchar_t は iOS と Android では 4 バイトだからです (また、Windows では文字列を UTF-16 で保存しなければならないということですか? Android/iOS では UTF-32 ですか?)
  • 抽象基本クラスを使用して文字列の抽象化を作成し、さまざまなプラットフォーム専用の内部格納を実装します。

最善の解決策は何ですか?ところで、文字列処理を抽象化する既存のクロスプラットフォーム ライブラリはありますか? (また、Unicode 文字列の読み取りとシリアル化)

(UPDATE: char* と std::string の違いに関する質問の部分を削除しました。)

4

3 に答える 3

6

私の質問の一部は、私の誤解、またはstringおよびwstringクラスが C++ でどのように機能するかを完全に理解していないことに起因しています (私は C# のバックグラウンドから来ています)。2 つの違いと長所と短所は、この素晴らしい回答std::wstring VS std::stringで説明されています。

string と wstring の仕組み

私にとって、string クラスと wstring クラスに関する唯一の最も重要な発見は、これらが意味的にエンコードされたテキストの一部ではなく、単に char または wchar_t の「文字列」を表しているということでした。それらは、テキストを表すというよりも、いくつかの文字列固有の操作 (append や substr など) を持つ単純なデータ配列に似ています。どちらも文字列エンコーディングの種類をまったく認識せず、各 char または wchar_t 要素を個別の文字として個別に処理します。

エンコーディング

ただし、ほとんどのシステムでは、次のような特殊文字を含む文字列リテラルから文字列を作成すると:

std::string s("ű");

ű文字はメモリ内で複数のバイトで表されますが、これは std::string class とは関係ありません。これは、文字列リテラルを UTF8 でエンコードできるコンパイラの機能です (ただし、すべてのコンパイラではありません)。(そして、L で始まる文字列リテラルは、コンパイラに応じて、UTF16 または UTF32 またはその他のいずれかで wchar_t-s によって表されます)。
したがって、文字列"ű"はメモリ内で0xC5 0xB1の 2 バイトで表現され、std::string クラスは、これらの 2 バイトが意味的に UTF8 の 1 文字 (1 つの Unicode コード ポイント) を意味することを認識しないため、サンプル コード:

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

次の結果が生成されます (コンパイラによっては、文字列リテラルを UTF8 として扱わないコンパイラもあれば、ソース ファイルのエンコーディングに依存するコンパイラもあります)。

2
�

size() 関数は 2 を返します。これは、std::string が知っている唯一のことは、2 バイト (2 文字) を格納することだけだからです。また、 substr も「プリミティブに」機能し、単一の char 0xC5を含む文字列を返します。これは、有効な UTF8 文字ではないため、� として表示されます (ただし、std::string には影響しません)。

そして、エンコーディングを処理するのは、単純なcoutDirectWriteなど、プラットフォームのさまざまなテキスト処理 API であることがわかります。

私のアプローチ

私のアプリケーションでは 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;

文字列は上記のクラスで保存され、生の UTF16 データはさまざまなプラットフォームの特定の API 関数に渡されます。現時点では、これらすべてが UTF16 エンコーディングをサポートしているようです。
実装は完璧ではないかもしれませんが、append、substr、および size 関数は適切に機能しているようです。私はまだ C++ での文字列処理の経験があまりないので、間違ったことを述べた場合は自由にコメント/編集してください。

于 2012-07-19T20:34:54.023 に答える
2

std::strings と char* の違いは、std::string クラスは C++ 機能を使用し、char* は使用しないことです。std::string は char のコンテナー クラスであり、それを使用するための便利なメソッドを定義します。char* は、操作できるメモリへのポインターです。

プラットフォームに依存しない基本クラスを探している場合は、QStringを参照してください。これは、プラットフォームに依存しない C++ の実装を目指す Qt ライブラリの一部です。また、これは 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 に答える