私の質問の一部は、私の誤解、または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 には影響しません)。
そして、エンコーディングを処理するのは、単純なcoutやDirectWriteなど、プラットフォームのさまざまなテキスト処理 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++ での文字列処理の経験があまりないので、間違ったことを述べた場合は自由にコメント/編集してください。