1. ホーム
  2. c++

[解決済み] C++でstd::stringをUTF-8で正しく使用するには?

2022-10-13 01:44:14

質問

私のプラットフォームはMacです。私はC++初心者で、中国語と英語を処理する個人的なプロジェクトに取り組んでいます。このプロジェクトでは、UTF-8 が好ましいエンコーディングです。

私は Stack Overflow でいくつかの投稿を読みましたが、それらの多くは std::string を使い、UTF-8 を扱うときは wchar_t がないため char8_t がないためです。

のような関数を適切に処理する方法については、どれも触れていません。 str[i] , std::string::size() , std::string::find_first_of() または std::regex のように、これらの関数はUTF-8に直面した場合、通常予期しない結果を返します。

を先に進めるべきでしょうか。 std::string にするか、それとも std::wstring ? もし、このまま std::string を使用する場合、上記の問題を処理するための最良の方法は何でしょうか?

どのように解決するのですか?

ユニコード用語集

Unicodeは広大で複雑なトピックです。あまり深入りしたくないのですが、簡単な用語集は必要です。

  1. コードポイント : コードポイントは、Unicodeの基本的な構成要素であり、コードポイントは、単に整数を 意味 . 整数部は 32 ビット (実際には 24 ビット) に収まり、意味は文字、発音区分符号、空白、記号、スマイリー、半旗、...、さらには「次の部分は右から左へ読む」(quot; the next portion reads right to left") である可能性があります。
  2. 書記素クラスタ : Grapheme Clusters は意味的に関連したコードポイントのグループです。例えば、unicode の旗は 2 つのコードポイントを関連付けることで表現されています。Grapheme Cluster はまた、いくつかのスクリプトで文字と発音記号を組み合わせるために使用されます。

これはUnicodeの基本です。ほとんどの現代言語では、各文字は 1 つのコード ポイントにマップされるため、コード ポイントと書記素クラスタの区別はほとんど無視できます (よく使われる文字と発音記号の組み合わせには、専用のアクセント記号付きの形式があります)。それでも、スマイリーやフラグなどを使用する場合は、その区別に注意を払う必要があります。


UTF プライマー

一般的なエンコーディングはUTF-8、UTF-16、UTF-32で、後者2つはリトルエンディアンとビッグエンディアンの両方が存在し、合計5つの一般的なエンコーディングが存在します。

UTF-X では、X は コードユニット であり、各コードポイントはその大きさに応じて1つまたは複数のコードユニットとして表現されます。

  • UTF-8:1~4個のコードユニット。
  • UTF-16:1または2個のコードユニット。
  • UTF-32。1つのコードユニット。

std::string そして std::wstring .

  1. を使用しないでください。 std::wstring は使わないでください。 wchar_t は Windows では 16 ビットしかありません)。 std::u32string を使います (別名 std::basic_string<char32_t> ).
  2. メモリ内表現( std::string または std::wstring はディスク上の表現 (UTF-8、UTF-16、または UTF-32) に依存しないので、境界 (読み取りと書き込み) で変換する必要があることを覚悟しておいてください。)
  3. が 32 ビットであるのに対し wchar_t はコードユニットが完全なコードポイントを表すことを保証しますが、それでも完全な Grapheme Cluster を表すわけではありません。

文字列を読んだり作ったりするだけであれば std::string または std::wstring .

問題はスライスとダイシングを開始したときに始まり、(1) コードポイント境界 (UTF-8 または UTF-16) と (2) 書記素クラスタ境界 に注意を払う必要があります。前者は自分で簡単に処理できますが、後者は Unicode を認識するライブラリを使用する必要があります。


ピッキング std::string または std::u32string ?

パフォーマンスが気になる場合は、おそらく std::string の方がメモリフットプリントが小さいため、より良いパフォーマンスを発揮すると思われます。ただし、中国語を多用する場合は契約が変更されるかもしれません。しかし、中国語を多用する場合は、その条件が変わるかもしれません。

Grapheme Clusters が問題でない場合は、次のようになります。 std::u32string は、物事を単純化できる利点があります。1コードユニット -> 1コードポイントとは、誤ってコードポイントを分割してしまうことがないように、すべての機能が std::basic_string のすべての機能がそのまま機能します。

ソフトウェアテイクとのインターフェイスの場合 std::string または char* / char const* であれば std::string を使うことで、前後の変換を避けることができます。そうしないと、面倒なことになります。


でのUTF-8 std::string .

UTF-8 は実際には std::string .

UTF-8エンコーディングは自己同期しており、ASCIIとの後方互換性があるため、ほとんどの操作は箱から出してすぐに動作します。

コードポイントがエンコードされているため、コードポイントを探しても、他のコードポイントの途中と偶然に一致することはあり得ません。

  • str.find('\n') が動作します。
  • str.find("...") 作品 バイト単位でのマッチングに 1 ,
  • str.find_first_of("\r\n") 作品 ASCII 文字を検索する場合 .

同様に regex はほとんどそのまま使えるはずです。一連の文字として ( "haha" ) は単なるバイトの列 ( "哈" ) であるため、基本的な検索パターンがそのまま使えるはずです。

しかし、文字クラス(例えば [:alphanum:] のような)文字クラスには注意が必要です。正規表現のフレーバーと実装によって、Unicode 文字にマッチする場合としない場合があります。

同様に、非 ASCII の "文字" にリピータを適用することに注意してください。 "哈?" は最後のバイトだけをオプションとみなすかもしれません。そのような場合は、括弧を使って繰り返されるバイト列を明確に区別してください。 "(哈)?" .

1 ルックアップのキーコンセプトは正規化と照合順序です。これはすべての比較操作に影響します。 std::string は常にバイト単位で比較(そしてソート)し、言語や使用法に特有の比較ルールを気にすることはありません。完全な正規化/照合順序を処理する必要がある場合は、ICU のような完全な Unicode ライブラリが必要です。