C ++ 11がUnicodeをサポートしていることを読んだり聞いたりしました。それに関するいくつかの質問:
- C ++標準ライブラリはどの程度Unicodeをサポートしていますか?
- ない
std::string
何それが必要ですか? - どうやって使うの?
- 潜在的な問題はどこにありますか?
C ++ 11がUnicodeをサポートしていることを読んだり聞いたりしました。それに関するいくつかの質問:
std::string
何それが必要ですか?回答:
C ++標準ライブラリはどの程度Unicodeをサポートしていますか?
ひどく。
Unicodeサポートを提供する可能性のあるライブラリー機能をざっとスキャンすると、次のリストが得られます。
最初のものを除いてすべてがひどいサポートを提供すると思います。他の質問を少し迂回した後で、さらに詳しく説明します。
ない
std::string
何それが必要ですか?
はい。C ++標準によると、これはstd::string
その兄弟とその兄弟が行うべきことです。
クラステンプレート
basic_string
は、シーケンスの最初の要素が位置0にある、さまざまな数の任意のcharのようなオブジェクトで構成されるシーケンスを格納できるオブジェクトを記述します。
まあ、それstd::string
はうまくいきますか。Unicode固有の機能はありますか?番号。
よろしいですか?おそらく違います。オブジェクトのstd::string
シーケンスとしては問題ありchar
ません。それは便利です。唯一の不満は、それがテキストの非常に低レベルのビューであり、標準のC ++が高レベルのビューを提供しないことです。
どうやって使うの?
char
オブジェクトのシーケンスとして使用します。それが別のふりをするふりをすることは苦痛で終わるにちがいない。
潜在的な問題はどこにありますか?
あらゆる所に?どれどれ...
文字列ライブラリ
文字列ライブラリはbasic_string
、標準が「charのようなオブジェクト」と呼ぶものの単なるシーケンスであるusを提供します。私はそれらをコード単位と呼びます。テキストの高レベルのビューが必要な場合、これはあなたが探しているものではありません。これは、シリアライズ/デシリアライズ/ストレージに適したテキストのビューです。
また、狭い世界と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 inを適切に分類することを期待しますu8"🍌"
かu8"\U0001F34C"
?これらの関数は入力として1つのコード単位しか使用しないため、機能する方法はありません。
これは、UTF-32の単一のコードユニットchar32_t
のみを使用している場合、適切なロケールで動作する可能性がありますU'\U0001F34C'
。
しかし、それはまだあなただけの単純なケースで変換得ることを意味toupper
し、tolower
「SS」を「ß」大文字の☦しかし:例えば、いくつかのドイツ語のロケールのために良い十分ではない、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
専門分野。これらの標準ファセットを組み合わせることで、以下のすべての変換が提供されます。(注:次のリストでは、左側のエンコードは常にシリアル化された文字列/ streambufで、右側のエンコードは常に非シリアル化された文字列/ streambufです。標準では両方向の変換が許可されています)。
codecvt_utf8<char16_t>
、及び、codecvt_utf8<wchar_t>
sizeof(wchar_t) == 2
codecvt_utf8<char32_t>
、codecvt<char32_t, char, mbstate_t>
および、codecvt_utf8<wchar_t>
sizeof(wchar_t) == 4
codecvt_utf16<char16_t>
、及び、codecvt_utf16<wchar_t>
sizeof(wchar_t) == 2
codecvt_utf16<char32_t>
、および、codecvt_utf16<wchar_t>
sizeof(wchar_t) == 4
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は、基本的な多言語プレーンのみをサポートするため、1996年に置き換えられたUnicode 1.0からのエンコーディングです。委員会が20年以上前に置き換えられたエンコーディングに焦点を合わせることが望ましいと考えた理由は、私にはわかりません‡。それはより多くのエンコーディングのサポートが悪いか何かであるようではありませんが、UCS-2はここに頻繁に現れます。
それchar16_t
は明らかにUTF-16コード単位を格納するためのものです。ただし、これは別の方法で考える標準の一部です。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-as-bytesのサポートは非常に優れています。BOMからエンディアンを検出したり、コードで明示的に選択したりできます。また、BOMがある場合とない場合の出力の生成もサポートします。
さらに興味深い変換の可能性はありません。UTF-8はデシリアライズされた形式としてサポートされないため、UTF-16バイトストリームまたは文字列をUTF-8文字列にデシリアライズする方法はありません。
そして、ここで、ナロー/ワイドの世界は、UTF / UCSの世界から完全に分離しています。古いスタイルのナロー/ワイドエンコーディングとUnicodeエンコーディングの間の変換はありません。
入出力ライブラリ
I / Oライブラリは、上記のwstring_convert
およびwbuffer_convert
機能を使用して、Unicodeエンコーディングでテキストを読み書きするために使用できます。標準ライブラリのこの部分でサポートする必要のある他の多くのものはないと思います。
正規表現ライブラリ
以前に、スタックオーバーフローでのC ++正規表現とUnicodeの問題について説明しました。ここではそれらすべてのポイントを繰り返すことはしませんが、C ++正規表現がレベル1のUnicodeサポートを持たないことだけを述べます。
それでおしまい?
はい、それだけです。それが既存の機能です。正規化やテキストセグメンテーションアルゴリズムのようにどこにも見られない多くのUnicode機能があります。
U + 1F4A9。C ++でより良いUnicodeサポートを取得する方法はありますか?
通常の容疑者:ICUおよびBoost.Locale。
†バイト文字列は、当然のことながら、バイトの文字列、つまりchar
オブジェクトです。ただし、常にオブジェクトの配列であるワイド文字列リテラルとは異なり、wchar_t
このコンテキストの「ワイド文字列」は必ずしもwchar_t
オブジェクトの文字列であるとは限りません。実際、標準では「ワイド文字列」の意味を明示的に定義していないため、使用方法から意味を推測する必要があります。標準的な用語はずさんでわかりにくいので、わかりやすくするために、自分の用語を使用します。
UTF-16のようなエンコーディングはのシーケンスとして格納できchar16_t
、エンディアンはありません。または、エンディアンを持つ一連のバイトとして格納することもできます(連続するバイトの各ペアはchar16_t
、エンディアンに応じて異なる値を表すことができます)。標準はこれらの形式の両方をサポートします。シーケンスはchar16_t
、プログラムの内部操作に役立ちます。バイトシーケンスは、そのような文字列を外部の世界と交換する方法です。したがって、「バイト」や「ワイド」の代わりに使用する用語は、「シリアライズ」および「デシリアライズ」されます。
"「Windowsですが!」あなたの🐎🐎を保持します。Windows 2000以降のすべてのバージョンのWindowsはUTF-16を使用しています。
☦はい、私はEszett(ẞ)のグロスについて知っていますが、すべてのドイツ語のロケールを夜に変更してßの大文字をẞにしても、これが失敗するケースは他にもたくさんあります。大文字のU + FB00ʟᴀᴛɪɴsʟɪɢᴀᴛᴜʀᴇupperғғを試してください。ʟᴀᴛɪɴᴄᴀᴘɪᴛᴀʟʟɪɢᴀᴛᴜʀᴇғғはありません。2つのFを大文字にします。またはU + 01F0ʟᴀᴛɪɴsᴍᴀʟʟʟᴇᴛᴛᴇʀᴊᴡɪᴛʜᴄᴀʀᴏɴ; 事前構成された資本はありません。大文字のJと結合するキャロンだけを大文字にします。
Unicodeは標準ライブラリではサポートされていません(サポートされているという意味での妥当な意味のため)。
std::string
はそれよりも優れていませんstd::vector<char>
。Unicode(またはその他の表現/エンコーディング)に完全に気づかず、単にそのコンテンツをblobとして扱います。バイト。
blobを保存して分類するだけでよい場合は、かなりうまくいきます。しかし、Unicode機能が必要になり次第(コードポイントの数、書記素の数)など)が必要になり次第、運が悪くなります。
これについて私が知っている唯一の包括的なライブラリはICUです。ただし、C ++インターフェースはJavaインターフェースから派生したため、慣用的ではありません。
Unicode NUL(U + 0000)はUTF-8のnullバイトであり、これがnullの唯一の方法であるため、UTF-8をa std::string
(またはa char[]
またはchar*
)に安全に格納できます。バイトはUTF-8で発生する可能性があります。したがって、UTF-8文字列はすべてのCおよびC ++文字列関数に従って適切に終了し、C ++ iostreamを使用してそれらをスリングすることができます(ロケールがUTF-8である限り、std::cout
およびを含みstd::cerr
ます)。
std::string
UTF-8 でできないことは、コードポイントで長さを取得することです。std::string::size()
文字列の長さをバイトで教えてくれますで示し。これは、UTF-8のASCIIサブセット内にいる場合にのみコードポイントの数と同じです。
コードポイントレベルでUTF-8文字列を操作する必要がある場合(つまり、保存して印刷するだけではない場合)、または内部nullバイトが多い可能性があるUTF-16を処理している場合は、ワイド文字列タイプ。
std::string
nullが埋め込まれたiostreamに問題なくスローできます。
c_str()
ているので全く壊れませんsize()
。壊れたAPI(つまり、ほとんどのCの世界のように埋め込まれたnullを処理できないもの)だけが壊れます。
c_str()
ため壊れc_str()
ます。これは、C文字列に埋め込みnullを含めることができないため、不可能です。
c_str()
今度は単にと同じdata()
、つまりすべてを返します。サイズを取るAPIはそれを消費する可能性があります。できないAPIはできません。
c_str()
結果にNUL charのようなオブジェクトが続くことを確認するわずかな違いがありますが、私はそうは思いdata()
ません。いいえ、data()
今もそうです。(もちろん、ターミネーター検索からサイズを推測する代わりにサイズを消費するAPIの場合、これは必要ありません)
C ++ 11には、Unicode用の新しいリテラル文字列型がいくつかあります。
残念ながら、標準ライブラリでの非均一エンコーディング(UTF-8など)のサポートはまだ悪いです。たとえば、UTF-8文字列の長さ(コードポイント単位)を取得するための良い方法はありません。
std::string
は問題なくUTF-8文字列を保持できlength
ますが、たとえば、メソッドはコードポイントの数ではなく、文字列のバイト数を返します。
ñ
「ラテン小文字N WITH TILDE」(U + 00F1)(1つのコードポイント)または「小ラテン文字N」( U + 006E)に続いて、2つのコードポイントである 'COMBINING TILDE'(U + 0303)。
LATIN SMALL LETTER N'
== と見なすかどうかにかかわらず、パーサーの仕様次第(U+006E) followed by 'COMBINING TILDE' (U+0303)
です。