WCHARS、エンコーディング、標準、および移植性
質問
以下は、そのような質問として資格がないかもしれません。範囲外の場合は、お気軽に去るように言ってください。ここでの問題は、基本的に「Cの基準を正しく理解しているのか、これが物事を進めるのに正しい方法ですか?」です。
C(したがって、C ++およびC ++ 0x)でのキャラクター処理の理解に関する明確化、確認、修正をお願いしたいと思います。まず、重要な観察:
移植性とシリアル化は直交概念です。
ポータブルなものはCのようなものです、 unsigned int
, wchar_t
. 。シリアル化可能なものは次のようなものです uint32_t
またはUTF-8。 「ポータブル」とは、同じソースを再コンパイルし、サポートされているすべてのプラットフォームで実現結果を得ることができることを意味しますが、バイナリ表現はまったく異なる場合があります(または、TCP-Over-Carrier Pigeonなど)。一方、シリアル化可能なものには常にあります 同じ 表現、たとえば、Windowsデスクトップ、電話、または歯ブラシで読むことができるPNGファイルなど。ポータブルなものは、I/Oを扱う内部のシリアル化可能なものです。ポータブルなものはタイプセーフであり、シリアル化可能なものはタイプのしゃれが必要です。u003C/preamble>
Cでのキャラクター処理に関しては、それぞれ携帯性とシリアル化に関連する2つのグループのグループがあります。
wchar_t
,setlocale()
,mbsrtowcs()
/wcsrtombs()
: C規格は「エンコーディング」について何も述べていません;実際、それはあらゆるテキストまたはエンコードプロパティに対して完全に不可知論されています。 「あなたのエントリポイントはそうですmain(int, char**)
;あなたはタイプを取得しますwchar_t
システムのすべての文字を保持できます。入力char-sequencesを読み取り、それらを実行可能なwstringにし、その逆にする関数を取得します。iconv()
およびUTF-8,16,32:明確で明確な固定エンコーディング間をトランスコードする関数/ライブラリ。 ICONVによって処理されるすべてのエンコーディングは、1つの例外を除いて、普遍的に理解され、合意されています。
cのポータブル、エンコーディングに依存しない世界の間の橋 wchar_t
ポータブルキャラクタータイプと決定論的な外の世界はそうです WCHAR-TとUTFの間のICONV変換.
したがって、私は常にエンコードに依存しないWSTRINGで文字列を内部に保存する必要がありますか、CRTとのインターフェース wcsrtombs()
, 、使用してください iconv()
シリアル化のために?概念的に:
my program
<-- wcstombs --- /==============\ --- iconv(UTF8, WCHAR_T) -->
CRT | wchar_t[] | <Disk>
--- mbstowcs --> \==============/ <-- iconv(WCHAR_T, UTF8) ---
|
+-- iconv(WCHAR_T, UCS-4) --+
|
... <--- (adv. Unicode malarkey) ----- libicu ---+
実際には、それは私のプログラムエントリポイントのために2つのボイラープレートラッパーを書くことを意味します。たとえば、C ++の場合:
// Portable wmain()-wrapper
#include <clocale>
#include <cwchar>
#include <string>
#include <vector>
std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc
int wmain(const std::vector<std::wstring> args); // user starts here
#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
extern "C" int main()
{
setlocale(LC_CTYPE, "");
int argc;
wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
return wmain(std::vector<std::wstring>(argv, argv + argc));
}
#else
extern "C" int main(int argc, char * argv[])
{
setlocale(LC_CTYPE, "");
return wmain(parse(argc, argv));
}
#endif
// Serialization utilities
#include <iconv.h>
typedef std::basic_string<uint16_t> U16String;
typedef std::basic_string<uint32_t> U32String;
U16String toUTF16(std::wstring s);
U32String toUTF32(std::wstring s);
/* ... */
これは、ICONVを使用してUTFへの明確に定義されたI/Oインターフェイスとともに、純粋な標準C/C ++のみを使用して、慣用的でポータブル、普遍的な、エンコーディングと存在するプログラムコアを書く正しい方法ですか? (ユニコードの正規化やディークリティック置換などの問題は、スコープの外側にあることに注意してください。実際に必要だと判断した後にのみ Unicode (他のコーディングシステムとは対照的に、あなたは空想するかもしれません)例えば、それらの詳細に対処する時が来ました。
更新
多くの非常に素敵なコメントに従って、いくつかの観察結果を追加したい:
アプリケーションが明示的にUnicodeテキストを処理したい場合は、
iconv
- コアと使用のコンバージョン部分uint32_t
/char32_t
-UCS-4を内部的に操縦します。Windows:広い文字列を使用することは一般的に問題ありませんが、コンソールとの相互作用(任意のコンソール、その点で)は限られているようです。
mbstowcs
本質的に役に立たない(些細な拡大以外)。たとえば、エクスプローラードロップから広く弦の議論を受け取るGetCommandLineW
+CommandLineToArgvW
作品(おそらく、Windowsには別のラッパーが必要です)。ファイルシステム:ファイルシステムは、エンコードの概念がないようで、単にヌル終端文字列をファイル名として使用するだけです。ほとんどのシステムはバイト文字列を取りますが、Windows/NTFSには16ビット文字列が必要です。どのファイルが存在するかを発見し、そのデータを処理するとき(例:
char16_t
有効なUTF16(裸のサロゲートなど)を構成しないシーケンスは、有効なNTFSファイル名です)。標準cfopen
すべてのNTFSファイルを開くことはできません。これは、可能なすべての16ビット文字列にマッピングされる変換が可能である可能性があるためです。 Windows固有の使用_wfopen
必要になる場合があります。帰結として、一般に、最初に「文字」の概念がないため、特定のファイル名を含む「文字の数」という明確な概念はありません。買い手責任負担。
解決
これは、純粋な標準c/c ++のみを使用して、慣用的でポータブル、普遍的な、エンコーディングと存在するプログラムコアを書く正しい方法ですか
いいえ、少なくともWindowsでプログラムを実行したい場合は、これらすべてのプロパティを満たす方法はまったくありません。 Windowsでは、ほぼどこでもCおよびC ++標準を無視し、独占的に協力する必要があります wchar_t
(必ずしも内部ではありませんが、システムへのすべてのインターフェイスで)。たとえば、最初から始めた場合
int main(int argc, char** argv)
コマンドライン引数のUnicodeサポートをすでに紛失しています。あなたは書かなければなりません
int wmain(int argc, wchar_t** argv)
代わりに、またはを使用します GetCommandLineW
機能はありません。これらはC標準で指定されていません。
すなわち、
- Windows上のUnicode対応プログラムは、コマンドライン引数、ファイルおよびコンソールI/O、ファイルとディレクトリの操作など、CおよびC ++標準を積極的に無視する必要があります。これは確かではありません 慣用. 。代わりに、boost.filesystemやqtなどのMicrosoft拡張機能またはラッパーを使用します。
- 移植性 特にUnicodeサポートのために、達成するのは非常に困難です。あなたは本当にあなたが知っていると思うすべてが間違っている可能性があることを本当に準備する必要があります。たとえば、ファイルを開くために使用するファイル名は、実際に使用されているファイル名とは異なる場合があり、2つの一見異なるファイル名が同じファイルを表している可能性があることを考慮する必要があります。 2つのファイルを作成した後 a と b, 、1つのファイルになってしまう可能性があります c, 、または2つのファイル d と e, 、そのファイル名は、OSに渡されたファイル名とは異なります。外部ラッパーライブラリまたはたくさんのものが必要です
#ifdef
s。 - アグノスティックのエンコード 通常、特にポータブルになりたい場合は、実際には機能しません。あなたはそれを知っている必要があります
wchar_t
WindowsなどのUTF-16コードユニットですchar
多くの場合、LinuxのUTF-8コードユニットです。エンコードアウェアリングは、多くの場合、より望ましい目標です。どのエンコードで作業するかを常に知っているか、それらを抽象化するラッパーライブラリを使用してください。
追加のライブラリとシステム固有の拡張機能を使用しても、多くの努力を払うことをいとわない限り、CまたはC ++でポータブルユニコード対応アプリケーションを構築することは完全に不可能であると結論付けなければならないと思います。残念ながら、ほとんどのアプリケーションは、「ギリシャ文字をコンソールに書く」や「システムで許可されているファイル名を正しい方法でサポートする」などの比較的単純なタスクで既に失敗し、そのようなタスクは真のUnicodeサポートに向けた最初の小さなステップにすぎません。
他のヒント
私は避けます wchar_t
タイプは、プラットフォームに依存しているため(定義による「シリアル化可能ではない」):WindowsのUTF-16、ほとんどのUNIXのようなシステムでUTF-32。代わりに、を使用します char16_t
および/または char32_t
C ++ 0x/c1xからのタイプ。 (新しいコンパイラを持っていない場合は、それらを次のように入力してください uint16_t
と uint32_t
今のところ。)
行う 機能を定義して、UTF-8、UTF-16、およびUTF-32関数を変換します。
しないでください 過負荷の狭い/ワイドバージョンを書きます 毎日 Windows APIのような文字列関数は-Aと-Wを使用しました。選ぶ 1 内部で使用することを好むエンコードを使用し、それに固執します。別のエンコードが必要なものについては、必要に応じて変換してください。
の問題 wchar_t
エンコーディングに依存しないテキスト処理が難しすぎて、避けるべきであるということです。あなたが言うように「純粋なc」に固執する場合、あなたはすべてのものを使用することができます w*
のような関数 wcscat
そして友人、しかし、あなたがもっと洗練されたことをしたいなら、あなたは深byに飛び込む必要があります。
ここにもっと難しいことがあります wchar_t
UTFエンコーディングのいずれかを選択した場合よりも、
解析JavaScript:識別剤は、BMPの外側に特定の文字を含めることができます(そして、この種の正確性を気にかけていると仮定します)。
HTML:どのように向きを変えますか
𐀀
一連の文字列にwchar_t
?テキストエディター:どのようにしてグラフェメクラスターの境界を見つけますか
wchar_t
ストリング?
文字列のエンコードがわかっている場合は、文字を直接調べることができます。エンコーディングがわからない場合は、文字列でやりたいことは何でも、どこかにライブラリ関数によって実装されることを期待しなければなりません。したがって、の移植性 wchar_t
私はそれを特に考えていないので、やや無関係です 使える データ・タイプ。
プログラムの要件が異なる場合があります wchar_t
あなたのためにうまくいくかもしれません。
とすれば iconv
「純粋な標準c/c ++」ではありません。あなたがあなた自身の仕様を満たしているとは思いません。
新しいものがあります codecvt
伴うファセット char32_t
と char16_t
そのため、ファセットがここにある場合、一貫性があり、1つのcharタイプ +エンコードを選択している限り、どのように間違っているかわかりません。
ファセットは22.5 [locale.stdcvt](N3242から)で説明されています。
これがあなたの要件の少なくともいくつかを満たしていないことを私は理解していません:
namespace ns {
typedef char32_t char_t;
using std::u32string;
// or use user-defined literal
#define LIT u32
// Communicate with interface0, which wants utf-8
// This type doesn't need to be public at all; I just refactored it.
typedef std::wstring_convert<std::codecvt_utf8<char_T>, char_T> converter0;
inline std::string
to_interface0(string const& s)
{
return converter0().to_bytes(s);
}
inline string
from_interface0(std::string const& s)
{
return converter0().from_bytes(s);
}
// Communitate with interface1, which wants utf-16
// Doesn't have to be public either
typedef std::wstring_convert<std::codecvt_utf16<char_T>, char_T> converter1;
inline std::wstring
to_interface0(string const& s)
{
return converter1().to_bytes(s);
}
inline string
from_interface0(std::wstring const& s)
{
return converter1().from_bytes(s);
}
} // ns
その後、コードを使用できます ns::string
, ns::char_t
, LIT'A'
& LIT"Hello, World!"
根底にある表現が何であるかを知らずに、無謀な放棄で。次に、使用します from_interfaceX(some_string)
それが必要なときはいつでも。グローバルロケールやストリームにも影響しません。ヘルパーは必要に応じて賢いことができます、例えば codecvt_utf8
「ヘッダー」を扱うことができます。これは、bomのようなトリッキーなものの標準的なものだと思います(ditto codecvt_utf16
).
実際、私は上記をできるだけ短くするように書いたが、あなたは本当にこのようなヘルパーが欲しいだろう:
template<typename... T>
inline ns::string
ns::from_interface0(T&&... t)
{
return converter0().from_bytes(std::forward<T>(t)...);
}
それぞれの3つのオーバーロードにアクセスできます [from|to]_bytes
メンバー、EGのようなものを受け入れます const char*
または範囲。