C++11 が Unicode をサポートしていると読んだり聞いたりしました。それについていくつか質問があります。
- C++ 標準ライブラリは Unicode をどの程度サポートしていますか?
std::string
期待通りの動作をしますか?- どうやって使うんですか?
- 潜在的な問題はどこにありますか?
ベストアンサー1
C++ 標準ライブラリは Unicode をどの程度サポートしていますか?
ひどい。
Unicode サポートを提供する可能性のあるライブラリ機能をざっと調べたところ、次のリストが得られました。
- 文字列ライブラリ
- ローカリゼーションライブラリ
- 入出力ライブラリ
- 正規表現ライブラリ
最初のもの以外はすべてサポートがひどいと思います。他の質問に少し寄り道した後、さらに詳しくお答えします。
std::string
期待通りの動作をしますか?
はい。C++ 標準によれば、std::string
およびその兄弟は次のようにすべきです。
クラス テンプレートは、
basic_string
シーケンスの最初の要素が位置 0 にある、さまざまな数の任意の char のようなオブジェクトで構成されるシーケンスを格納できるオブジェクトを記述します。
そうですね、std::string
それで問題ありません。Unicode 固有の機能は提供されますか? いいえ。
そうすべきでしょうか? おそらくそうではありません。オブジェクトstd::string
のシーケンスとしては問題ありchar
ません。これは便利です。唯一の問題は、これがテキストの非常に低レベルのビューであり、標準の C++ では高レベルのビューが提供されていないことです。
どうやって使うんですか?
それをオブジェクトのシーケンスとして使用しますchar
。他の何かであるかのように装うと、必ず苦痛に終わります。
潜在的な問題はどこにありますか?
あちこちに?そうだな...
文字列ライブラリ
文字列ライブラリはbasic_string
、標準で「char のようなオブジェクト」と呼ばれるものの単なるシーケンスである を提供します。私はこれをコード ユニットと呼んでいます。テキストの高レベルのビューが必要な場合、これは探しているものではありません。これは、シリアル化/デシリアル化/ストレージに適したテキストのビューです。
また、狭い世界と Unicode の世界の間のギャップを埋めるために使用できる C ライブラリのツールもいくつか提供します: c16rtomb
/mbrtoc16
およびc32rtomb
/ mbrtoc32
。
ローカリゼーションライブラリ
ローカリゼーション ライブラリは、依然として、これらの「char のようなオブジェクト」の 1 つが 1 つの「文字」に等しいと考えています。これはもちろんばかげており、ASCII のような Unicode の小さなサブセット以外では、多くのものを適切に動作させることが不可能になります。
たとえば、標準がヘッダーで「便利なインターフェース」と呼んでいるものを考えてみましょう<locale>
。
template <class charT> bool isspace (charT c, const locale& loc);
template <class charT> bool isprint (charT c, const locale& loc);
template <class charT> bool iscntrl (charT c, const locale& loc);
// ...
template <class charT> charT toupper(charT c, const locale& loc);
template <class charT> charT tolower(charT c, const locale& loc);
// ...
これらの関数が、たとえば U+1F34C ʙᴀɴᴀɴᴀ を またはu8"��"
のように適切に分類すると期待できますu8"\U0001F34C"
か? これらの関数は入力として 1 つのコード単位しか受け取らないため、それが機能する可能性はまったくありません。
のみを使用した場合、これは適切なロケールで機能する可能性がありますchar32_t
:U'\U0001F34C'
は UTF-32 の単一のコード単位です。
ただし、それでも と を使用した単純な大文字小文字の変換しか実行できないことを意味しtoupper
、tolower
これは、たとえば、一部のドイツ語ロケールでは不十分です。大文字の「ß」は「SS」☦ になりますが、 1 つの
文字
toupper
コード単位しか返すことができません 。
次は、wstring_convert
/wbuffer_convert
と標準コード変換ファセットです。
wstring_convert
は、あるエンコーディングの文字列を別のエンコーディングの文字列に変換するために使用されます。この変換には 2 つの文字列型が関係しており、標準ではバイト文字列とワイド文字列と呼ばれています。これらの用語は非常に誤解を招くため、代わりにそれぞれ「シリアル化」と「デシリアル化」を使用することを好みます†。
変換するエンコーディングは、テンプレート型引数として渡される codecvt (コード変換ファセット) によって決定されますwstring_convert
。
wbuffer_convert
同様の機能を実行しますが、
バイト
シリアル化ストリーム バッファーをラップする
ワイドな デシリアライズ ストリーム バッファーとして実行されます。すべての I/O は、codecvt 引数で指定されたエンコードとの間で変換を行い、基礎となる
バイト
シリアル化ストリーム バッファーを介して実行されます 。書き込みは、そのバッファーにシリアル化してから書き込み、読み取りはバッファーに読み取りしてからデシリアライズします。
標準では、これらの機能で使用するための codecvt クラス テンプレートがいくつか提供されています: 、、、codecvt_utf8
およびいくつかの特殊化。これらの標準ファセットを組み合わせることで、次のすべての変換が提供されます。(注: 次のリストでは、左側のエンコーディングは常にシリアル化された文字列/ストリーム バッファであり、右側のエンコーディングは常に逆シリアル化された文字列/ストリーム バッファです。標準では両方向の変換が許可されています)。codecvt_utf16
codecvt_utf8_utf16
codecvt
- UTF-8 ↔ UCS-2
codecvt_utf8<char16_t>
、codecvt_utf8<wchar_t>
およびsizeof(wchar_t) == 2
; - UTF-8 ↔ UTF-32 (
codecvt_utf8<char32_t>
、、codecvt<char32_t, char, mbstate_t>
および;codecvt_utf8<wchar_t>
を含むsizeof(wchar_t) == 4
) - UTF-16 ↔ UCS-2
codecvt_utf16<char16_t>
、codecvt_utf16<wchar_t>
およびsizeof(wchar_t) == 2
; - UTF-16 ↔ UTF-32
codecvt_utf16<char32_t>
、codecvt_utf16<wchar_t>
およびsizeof(wchar_t) == 4
; - UTF-8 ↔ UTF-16 (
codecvt_utf8_utf16<char16_t>
、、およびcodecvt<char16_t, char, mbstate_t>
、codecvt_utf8_utf16<wchar_t>
) の場合sizeof(wchar_t) == 2
、; - 狭い↔広い
codecvt<wchar_t, char_t, mbstate_t>
- では何も起こりません
codecvt<char, char, mbstate_t>
。
これらのいくつかは便利ですが、扱いにくいものもたくさんあります。
まず第一に、なんて高そうな代理なんだ!その命名方法は乱雑だ。
また、UCS-2 のサポートも多数あります。UCS-2 は Unicode 1.0 のエンコーディングで、基本的な多言語プレーンしかサポートしていないため、1996 年に廃止されました。委員会が 20 年以上前に廃止されたエンコーディングに重点を置くことをなぜ望ましいと考えたのかはわかりません‡。エンコーディングのサポートが増えることが悪いというわけではありませんが、UCS-2 がここで頻繁に登場します。
明らかに UTF-16 コード単位を格納するためのものだと言えますchar16_t
。しかし、これは標準の一部で、そうではないと考えられている部分です。はcodecvt_utf8<char16_t>
UTF-16 とは何の関係もありません。たとえば、はwstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
正常にコンパイルされますが、無条件に失敗します。入力は UCS-2 文字列 として扱われますがu"\xD83C\xDF4C"
、UTF-8 は 0xD800-0xDFFF の範囲の値をエンコードできないため、UTF-8 に変換できません。
UCS-2 に関しては、これらのファセットを使用して UTF-16 バイト ストリームから UTF-16 文字列に読み込む方法はありません。UTF-16 バイトのシーケンスがある場合、それを の文字列に逆シリアル化することはできませんchar16_t
。これは、多かれ少なかれ恒等変換であるため、驚くべきことです。しかし、さらに驚くべきことは、 を使用した UTF-16 ストリームから UCS-2 文字列への逆シリアル化がサポートされているという事実です。codecvt_utf16<char16_t>
これは実際には非可逆変換です。
ただし、UTF-16 バイトのサポートは非常に優れています。BOM からエンディアンの検出、またはコード内での明示的な選択をサポートしています。また、BOM の有無にかかわらず出力を生成することもサポートしています。
さらに興味深い変換の可能性がいくつかあります。UTF-8 はデシリアライズされた形式としてサポートされていないため、UTF-16 バイト ストリームまたは文字列から UTF-8 文字列にデシリアライズする方法はありません。
ここで、ナロー/ワイドの世界は UTF/UCS の世界とは完全に分離されています。旧式のナロー/ワイド エンコーディングと Unicode エンコーディングの間には変換はありません。
入出力ライブラリ
wstring_convert
I/O ライブラリは、上記のおよび機能を使用して、Unicode エンコードのテキストの読み取りと書き込みに使用できますwbuffer_convert
。標準ライブラリのこの部分でサポートする必要があるものは他にあまりないと思います。
正規表現ライブラリ
私は問題について詳しく説明しましたC++ 正規表現と Unicode以前、Stack Overflow でその点について言及しました。ここでそれらの点をすべて繰り返すつもりはありませんが、C++ 正規表現にはレベル 1 Unicode サポートがないことを述べます。これは、どこでも UTF-32 を使用することなく正規表現を使用できるようにするための最低限の要件です。
それでおしまい?
はい、その通りです。それが既存の機能です。正規化やテキスト分割アルゴリズムなど、どこにも見られない Unicode 機能がたくさんあります。
1F4A9 ...C++ でより優れた Unicode サポートを実現する方法はありますか?
† バイト文字列は、当然ながら、バイト、つまりchar
オブジェクトの文字列です。しかし、ワイド文字列リテラルは常にオブジェクトの配列ですがwchar_t
、このコンテキストでの「ワイド文字列」は必ずしもオブジェクトの文字列であるとは限りませんwchar_t
。実際、標準では「ワイド文字列」の意味が明示的に定義されていないため、使用方法から意味を推測するしかありません。標準の用語はいい加減でわかりにくいため、明確さを期して独自の用語を使用しています。
UTF-16 のようなエンコーディングは、 のシーケンスとして保存できますchar16_t
。この場合、エンディアンはありません。または、 のシーケンスとして保存できます。この場合、エンディアンがあります (連続する各バイト ペアは、char16_t
エンディアンに応じて異なる値を表すことができます)。標準では、これら両方の形式がサポートされています。 のシーケンスは、char16_t
プログラムの内部操作に便利です。バイト シーケンスは、このような文字列を外部の世界と交換する方法です。したがって、ここでは「バイト」と「ワイド」の代わりに「シリアル化」と「デシリアル化」という用語を使用します。
‡ 「でもWindowsだ!」と言いたくなったら、����Windows 2000 以降のすべてのバージョンの Windows では UTF-16 が使用されます。
☦ はい、知っています大きなエッセンシャル(ẞ) ですが、ドイツ語のロケールすべてを一晩で ß を ẞ に大文字に変更したとしても、これが失敗するケースは他にもたくさんあります。U+FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ を大文字にしてみてください。ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ はなく、単に 2 つの F に大文字になるだけです。または、U+01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ。合成済みの大文字はなく、大文字の J と結合キャロンに大文字化されるだけです。