以下は、SOの質問に該当しない場合があります。範囲外の場合は、遠慮なく遠慮なく教えてください。ここでの質問は、「C標準を正しく理解していますか?これは物事を進める正しい方法ですか?」
C(したがってC++およびC++ 0x)での文字処理の理解について、明確化、確認、および修正をお願いしたいと思います。まず、重要な観察:
移植性とシリアライゼーションは直交する概念です。
移植可能なものは、C、_unsigned int
_、_wchar_t
_などです。シリアライズ可能なものは、_uint32_t
_やUTF-8などです。 「移植可能」とは、同じソースを再コンパイルして、サポートされているすべてのプラットフォームで動作する結果を得ることができることを意味しますが、バイナリ表現は完全に異なる場合があります(または、TCP overキャリアピジョンなど存在しない場合もあります)。一方、シリアライズ可能なものは常にsame表現を持ちます。 Windowsデスクトップ、携帯電話、または歯ブラシで読むことができるPNGファイル。ポータブルなものは内部であり、I/Oを処理するシリアライズ可能なものです。移植可能なものはタイプセーフであり、シリアライズ可能なものは型パンニングを必要とします。 </ preamble>
Cでの文字処理に関しては、移植性と直列化にそれぞれ関連する2つのグループがあります。
_wchar_t
_、setlocale()
、mbsrtowcs()
/wcsrtombs()
:C標準では「エンコーディング」について何も述べられていません;実際、それはテキストやエンコーディングプロパティに完全に依存しません。 「エントリポイントはmain(int, char**)
です。システムのすべての文字を保持できる型_wchar_t
_を取得します。入力文字シーケンスを読み取り、実行可能なwstringに変換する関数を取得します逆。
iconv()
およびUTF-8,16,32:明確に定義された、固定された固定エンコーディング間でトランスコードする関数/ライブラリ。 iconvによって処理されるすべてのエンコーディングは、1つの例外を除いて、広く理解され、合意されています。
_wchar_t
_ポータブル文字型を使用した、Cのポータブルでエンコードに依存しない世界と確定的な外部の世界との間の架け橋はWCHAR-TとUTF間のiconv変換です。
したがって、常に文字列をエンコードに依存しないwstringに内部的に保存し、wcsrtombs()
を介してCRTとインターフェイスし、シリアル化に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);
/* ... */
_
これは、純粋な標準C/C++とiconvを使用したUTFへの明確に定義されたI/Oインターフェースのみを使用して、エンコーディングにとらわれない、慣用的で移植性のある慣用的なプログラムコアを作成する正しい方法ですか? (Unicodeの正規化や分音記号の置換などの問題は範囲外です。実際に必要であると判断した後でのみnicode(ファンシーかもしれない他のコーディングシステムとは対照的に)、それらに対処する時が来ました詳細、例えばlibicuのような専用ライブラリを使用する。)
更新
多くの非常に素晴らしいコメントに続いて、私はいくつかの観察を追加したいと思います:
アプリケーションが明示的にUnicodeテキストを処理する場合は、コアのiconv
- conversion部分を作成し、UCS-4で内部的に_uint32_t
_/_char32_t
_- stringsを使用する必要があります。
Windows:ワイド文字列の使用は一般的には問題ありませんが、コンソール(つまり、任意のコンソール)との対話は制限されているようです。賢明なマルチバイトコンソールエンコーディングはサポートされていないようで、mbstowcs
は本質的に役に立たない(ささいな拡大以外)。エクスプローラードロップからワイド文字列引数を受け取ると、GetCommandLineW
+ CommandLineToArgvW
が機能します(おそらく、Windows用の個別のラッパーがあるはずです)。
ファイルシステム:ファイルシステムはエンコーディングの概念を持たないようで、単にnullで終了する文字列をファイル名として受け取ります。ほとんどのシステムはバイト文字列を取りますが、Windows/NTFSは16ビット文字列を取ります。存在するファイルを検出するとき、およびそのデータを処理するとき(たとえば、有効なUTF16を構成しない_char16_t
_シーケンス(ネイキッドサロゲートなど)は有効なNTFSファイル名です)に注意する必要があります。標準Cのfopen
では、すべての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標準ではいずれも指定されていません。
すなわち、
#ifdef
sが必要です。wchar_t
はWindowsではUTF-16コード単位であり、char
はLinuxでは多くの場合(ボットとは限りません)UTF-8コード単位であることを知っておく必要があります。多くの場合、エンコーディングを意識することがより望ましい目標です。どのエンコーディングを使用するかを常に把握しておくか、それらを抽象化するラッパーライブラリを使用してください。追加のライブラリーとシステム固有の拡張機能を使用し、それに多大な労力を費やすことを望まない限り、CまたはC++でポータブルUnicode対応アプリケーションを構築することは完全に不可能であると私は結論づけなければならないと思います。残念ながら、ほとんどのアプリケーションは「ギリシャ語の文字をコンソールに書き込む」、「システムで許可されているファイル名を正しくサポートする」などの比較的単純なタスクで失敗し、そのようなタスクは真のUnicodeサポートに向けた最初の小さなステップにすぎません。
wchar_t
タイプはプラットフォームに依存しているため(定義によって「シリアライズ可能」ではない)、WindowsではUTF-16、ほとんどのUnixライクなシステムではUTF-32であるため、私は避けます。代わりに、C++ 0x/C1xのchar16_t
およびchar32_t
タイプを使用してください。 (新しいコンパイラがない場合は、現時点ではuint16_t
およびuint32_t
としてtypedefしてください。)
[〜#〜] do [〜#〜] UTF-8、UTF-16、およびUTF-32関数間で変換する関数を定義します。
DO N'T Windows APIが-Aおよび-Wで行ったようなevery文字列関数のオーバーロードされたナロー/ワイドバージョンを書き込みます。内部で使用するone優先エンコーディングを選択し、それに固執します。別のエンコーディングが必要な場合は、必要に応じて変換してください。
wchar_t
の問題は、エンコードに依存しないテキスト処理が非常に難しいため、回避する必要があることです。あなたが言うように「純粋なC」に固執する場合、wcscat
および友人のようなw*
関数のすべてを使用できますが、より高度なことをしたい場合は、深淵。
以下は、UTFエンコーディングの1つを選択する場合よりも、wchar_t
を使用する場合の方がはるかに難しいものです。
Javascriptの解析:識別子には、BMPの外に特定の文字を含めることができます(この種の正確性に関心があると想定してください)。
HTML:どのように𐀀
をwchar_t
の文字列に変換しますか?
テキストエディター:wchar_t
文字列で書記素クラスターの境界をどのように見つけますか?
文字列のエンコーディングがわかっていれば、文字を直接調べることができます。エンコーディングがわからない場合は、文字列を使用して実行する処理がライブラリ関数によってどこかに実装されていることを期待する必要があります。したがって、wchar_t
の移植性は、特に便利データ型であるとは考えていないため、あまり関係ありません。
プログラム要件は異なる場合があり、wchar_t
が適切に機能する場合があります。
iconv
が「純粋な標準C/C++」ではないことを考えると、自分の仕様を満たしているとは思えません。
_char32_t
_と_char16_t
_に新しいcodecvt
ファセットが追加されたので、一貫性があり、ファセットの場合は1つの文字タイプ+エンコーディングを選択する限り、どのように間違っているかはわかりませんあります。
ファセットについては、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(同種の_codecvt_utf16
_)などのトリッキーなものからのStandardeseだと思います。
実際、私は上記を可能な限り短くするために書きましたが、あなたは本当にこのようなヘルパーが欲しいでしょう:
_template<typename... T>
inline ns::string
ns::from_interface0(T&&... t)
{
return converter0().from_bytes(std::forward<T>(t)...);
}
_
これにより、各_[from|to]_bytes
_メンバーの3つのオーバーロードにアクセスできるようになり、たとえば、 _const char*
_または範囲。