1. ホーム
  2. c++

[解決済み] C++11でUnicodeはどの程度サポートされていますか?

2022-03-03 07:39:10

質問

C++11がUnicodeをサポートしていると読み、聞きました。それについていくつか質問があります。

  • C++の標準ライブラリは、どの程度Unicodeをサポートしていますか?
  • std::string はどうすればいいのでしょうか?
  • どのように使用するのですか?
  • 問題が発生しそうな箇所はどこですか?

解決方法は?

<ブロッククオート

C++の標準ライブラリは、どの程度ユニコードをサポートしていますか?

ひどいもんだ。

Unicodeをサポートする可能性のあるライブラリ設備をざっと調べてみると、こんなリストが出来上がりました。

  • 文字列ライブラリ
  • ローカライゼーションライブラリ
  • 入出力ライブラリ
  • 正規表現ライブラリ

最初のものを除いては、ひどいサポートを提供していると思います。他の質問を少し回り道した後に、より詳しく説明します。

<ブロッククオート

はたして std::string はどうすればいいのでしょうか?

はい。C++の規格によると、これは std::string とその兄弟が行うべきことです。

クラステンプレート basic_string は、任意の数のchar型オブジェクトからなるシーケンスを格納することができるオブジェクトを記述しています。

さて。 std::string はうまくいきますね。これはUnicode固有の機能を提供しているのでしょうか?いいえ。

そうすべきでしょうか?おそらくないでしょう。 std::string が連続しても問題ありません。 char オブジェクトを作成します。これは便利です。唯一の悩みは、これが非常に低レベルのテキスト表示であり、標準C++がより高レベルのものを提供していないことです。

<ブロッククオート

どうやって使うの?

の連続として使用します。 char 他のものだと思い込むと痛い目にあいます。

<ブロッククオート

潜在的な問題はどこにあるのか?

あちこちで?そうですね...

文字列ライブラリ

文字列ライブラリは basic_string これは、標準では「char-like objects」と呼ばれているもののシーケンスに過ぎません。私はこれをコードユニットと呼んでいます。もしあなたがテキストのハイレベルなビューを求めているなら、これはあなたが探しているものではありません。これは、シリアライズ/デシリアライズ/ストレージに適したテキストのビューです。

また、狭い世界とUnicodeの世界の橋渡しに使えるCライブラリのツールもいくつか提供されています。 c16rtomb / mbrtoc16c32rtomb / mbrtoc32 .

ローカライゼーションライブラリ

ローカライゼーションライブラリは、これらの"char-like object"の1つが1つの"character"に等しいとまだ信じているのです。これはもちろん馬鹿げていて、ASCIIのようなUnicodeの小さなサブセットを超えて多くのものを適切に動作させることを不可能にしています。

例えば、この規格で "convenience interfaces" と呼ばれている <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では1つのコードユニットとなります。

しかし、これでもまだ touppertolower は、例えば、ドイツ語のロケールによっては十分ではありません: "ß" の大文字を "SS"☦ にしますが toupper は1つしか返せません。 <ストライク 文字 コード単位。

次のページ wstring_convert / wbuffer_convert と標準的なコード変換ファセットです。

wstring_convert は、あるエンコーディングの文字列を別のエンコーディングの文字列に変換するために使用されます。この変換には2つの文字列型があり、標準ではバイト文字列とワイド文字列と呼んでいます。これらの用語は本当に誤解を招くので、私は代わりにそれぞれ "serialized" と "deserialized" を使うことを好んでいます†。

にテンプレート型の引数として渡される codecvt (コード変換ファセット) によって、変換するエンコーディングが決定されます。 wstring_convert .

wbuffer_convert は同様の機能を持ちますが <ストライク 広い を包むデシリアライズストリームバッファ。 <ストライク バイト シリアライズされたストリームバッファです。すべてのI/Oは、基礎となる バイト シリアライズされたストリームバッファで、codecvt引数で指定されたエンコーディングに変換されます。書き込みはそのバッファにシリアライズし、そこから書き込みを行い、読み込みはバッファに読み込み、そこからデシリアライズします。

規格では、これらの機能を利用するためのcodecvtクラスのテンプレートがいくつか提供されています。 codecvt_utf8 , codecvt_utf16 , codecvt_utf8_utf16 と、いくつかの codecvt の特殊化です。これらの標準ファセットは、以下のすべての変換を提供します。(注意: 以下のリストでは、左側のエンコーディングは常にシリアライズされた文字列/streambufであり、右側のエンコーディングは常にデシリアライズされた文字列/streambufです; 標準は両方の方向の変換を許可します)。

  • 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があまりにも頻繁に登場するのです。

と言いたいところですが char16_t は明らかにUTF-16のコードユニットを格納するためのものです。しかし、これは規格の一部で、そうではないと考えられています。 codecvt_utf8<char16_t> はUTF-16とは関係ない。例えば wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C") は正常にコンパイルされますが、無条件に失敗します。 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> これは実際には非可逆的な変換です。

エンディアンをBOMから検出したり、コード内で明示的に選択したりすることができます。また、BOMの有無にかかわらず、出力を生成することもできます。

もっと面白い変換の可能性がないわけではありません。UTF-16のバイトストリームや文字列からUTF-8の文字列にデシリアライズする方法はありません。

そして、ここで narrow/wide の世界は、UTF/UCS の世界と完全に分離しています。旧来のnarrow/wideエンコーディングとUnicodeエンコーディングの間の変換は一切ありません。

入出力ライブラリ

I/Oライブラリは、Unicodeエンコーディングでテキストの読み書きを行うために wstring_convertwbuffer_convert という施設があります。この標準ライブラリの部分でサポートする必要があるものは、他にはあまりないと思います。

正規表現ライブラリ

の問題点を解説してきました。 C++正規表現とユニコード を以前Stack Overflowで紹介しました。しかし、C++正規表現にはレベル1のUnicodeサポートがなく、あらゆる場所でUTF-32を使用することなく使用できるようにするための最低条件であることを述べたいと思います。

<ブロッククオート

それだけですか?

そうです、それです。それが既存の機能です。正規化とかテキスト分割のアルゴリズムとか、どこにもないUnicodeの機能がたくさんあります。

<ブロッククオート

U+1F4A9 . C++でもっと良いUnicodeサポートを得る方法はないのでしょうか?

いつものことですが ICU そして ブースト.ロケール .


† バイト列は、当然のことながら、バイトの文字列である。 char オブジェクトを作成します。ただし 幅の広い文字列リテラル の配列であり、常に wchar_t オブジェクトの文字列ですが、この文脈では、quot; wide string" は必ずしも wchar_t オブジェクトを作成します。実際、規格では "wide string" の意味を明示的に定義していないので、使い方から意味を推測することになる。標準の用語は杜撰で分かりにくいので、分かりやすくするために私独自の用語を使っています。

UTF-16のようなエンコーディングは、以下のようなシーケンスとして格納することができます。 char16_t または、エンディアンを持つバイト列として格納することもできます(連続したバイトの組は、それぞれ異なる char16_t の値はエンディアンに依存する)。本標準規格では、これら両方の形式をサポートしている。のシーケンスは char16_t は、プログラム内部での操作に便利です。バイト列は、そのような文字列を外部と交換するための方法です。このように、quot;byte" とquot;wide" の代わりに使う用語は、quot;serialized" とquot;deserialized" です。

しかし、Windowsは......」と言いそうになったら、ちょっと待ってください。 ???????? . Windows 2000 以降の Windows はすべて UTF-16 を使用します。

☦ そうなんです。 グローバルエスケット (ǩ)ですが、仮にすべてのドイツ語ロケールを一晩で大文字の ß を ẞ に変更したとしても、失敗するケースは他にたくさんあります。U+FB00 ʟᴀǑɴ sǐᴀʟʟɢᴜʀᴇғғと大文字に変えてみてください。ʟᴀᴛɪᴄᴀᴘɪᴀʟɪⴀᴜ⧑ɫғҝ Fが2つになるだけで、大文字になっています。U+01F0 ʟᴀᴛɪɴ ʟᴇᴊ ᴡɪᴄᴀʀᴏ⇄;は大文字Jと結合カロンまでアッ パーコースティスとなるだけです。