66

以下は、SOの質問とは見なされない場合があります。範囲外の場合は、遠慮なくお立ち寄りください。ここでの質問は、基本的に「C規格を正しく理解しているか、これが正しい方法であるか」です。

C(したがって、C++およびC++ 0x)での文字処理についての私の理解について、明確化、確認、および修正をお願いしたいと思います。まず、重要な観察事項:

移植性とシリアル化は直交する概念です。

ポータブルなものは、C 、、、のようなものunsigned intですwchar_t。シリアル化可能なものは、uint32_tまたはUTF-8のようなものです。「ポータブル」とは、サポートされているすべてのプラットフォームで同じソースを再コンパイルして動作する結果を得ることができることを意味しますが、バイナリ表現は完全に異なる場合があります(または、TCP-伝書鳩など、存在しない場合もあります)。一方、シリアル化可能なものは常に同じ表現になります。たとえば、Windowsデスクトップ、電話、または歯ブラシで読み取ることができるPNGファイルです。ポータブルなものは内部であり、シリアル化可能なものはI/Oを処理します。ポータブルなものは型の安全であり、シリアル化可能なものは型のパンニングが必要です。</ preamble>

Cでの文字処理に関しては、移植性とシリアル化にそれぞれ関連する2つのグループがあります。

  • wchar_t、、/ : C標準setlocale()「エンコーディング」について何も述べていません; 実際、テキストやエンコーディングのプロパティにはまったく依存しません。「エントリポイントは、システムのすべての文字を保持できる型を取得します。入力文字シーケンスを読み取り、それらを実行可能なwstringにする関数を取得します。その逆も同様です。mbsrtowcs()wcsrtombs()main(int, char**)wchar_t

  • iconv()およびUTF-8,16,32:明確に定義された明確な固定エンコーディング間でトランスコードする関数/ライブラリ。iconvによって処理されるすべてのエンコーディングは、1つの例外を除いて、普遍的に理解され、合意されています。

wchar_tポータブルな文字タイプを持つCのポータブルでエンコードにとらわれない世界と、決定論的な外の世界との間の架け橋は、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);

/* ... */

これは、純粋な標準C / C ++のみを使用し、iconvを使用してUTFへの明確に定義されたI / Oインターフェイスを使用して、慣用的でポータブル、ユニバーサル、エンコーディングに依存しないプログラムコアを作成する正しい方法ですか?(Unicodeの正規化や発音区別符号の置換などの問題は範囲外であることに注意してください。 (他のコーディングシステムとは対照的に)実際にUnicodeが必要であると判断した後でのみ、専用ライブラリを使用するなど、これらの詳細に対処する必要があります。 libicuのように。)

更新

多くの非常に素晴らしいコメントに続いて、いくつかの所見を追加したいと思います。

  • アプリケーションで明示的にUnicodeテキストを処理する場合iconvは、コアの-conversion部分を作成し、UCS-4で内部的にuint32_t/ char32_t-stringsを使用する必要があります。

  • Windows:幅の広い文字列を使用することは一般的に問題ありませんが、コンソール(さらに言えば、任意のコンソール)との対話は制限されているようです。これは、実用的なマルチバイトコンソールエンコーディングがサポートされていないようであり、mbstowcs本質的に役に立たないためです(その他些細な拡大よりも)。たとえば、Explorer-dropと一緒にワイドストリング引数を受け取ると、GetCommandLineW+CommandLineToArgvWが機能します(おそらく、Windows用に別のラッパーが必要です)。

  • ファイルシステム:ファイルシステムにはエンコーディングの概念がないようで、ファイル名としてnullで終了する文字列を使用するだけです。ほとんどのシステムはバイト文字列を取りますが、Windows/NTFSは16ビット文字列を取ります。存在するファイルを検出するとき、およびそのデータを処理するときは注意する必要があります(たとえばchar16_t、有効なUTF16を構成しないシーケンス(たとえば、裸のサロゲート)は有効なNTFSファイル名です)。標準Cfopenは、すべての可能な16ビット文字列にマップされる可能性のある変換がないため、すべてのNTFSファイルを開くことはできません。Windows固有の使用_wfopenが必要になる場合があります。当然の結果として、そもそも「文字」の概念がないため、一般に、特定のファイル名を構成する「文字数」の明確な概念はありません。買い手責任負担。

4

4 に答える 4

22

これは、純粋な標準 C/C++ のみを使用して、慣用的で、移植可能で、普遍的で、エンコードに依存しないプログラム コアを作成する正しい方法ですか?

いいえ、少なくともプログラムを Windows で実行したい場合は、これらすべての特性を満たす方法はまったくありません。Windows では、ほとんどすべての場所で C および C++ 標準を無視し、排他的にwchar_t(必ずしも内部ではなく、システムへのすべてのインターフェイスで) 作業する必要があります。たとえば、

int main(int argc, char** argv)

コマンドライン引数の Unicode サポートはすでに失われています。あなたは書く必要があります

int wmain(int argc, wchar_t** argv)

代わりに、またはGetCommandLineWC 標準で指定されていない関数を使用します。

すなわち、

  • Windows 上の Unicode 対応プログラムは、コマンド ライン引数、ファイルとコンソールの I/O、またはファイルとディレクトリの操作などについて、C および C++ 標準を積極的に無視する必要があります。これは確かに慣用的ではありません。代わりに、Boost.Filesystem や Qt などの Microsoft 拡張機能またはラッパーを使用してください。
  • 特に Unicode サポートの場合、移植性を実現するのは非常に困難です。自分が知っていると思っていることはすべて間違っている可能性があることを覚悟しておく必要があります。たとえば、ファイルを開くために使用するファイル名が実際に使用されるファイル名と異なる可能性があること、および一見異なる 2 つのファイル名が同じファイルを表している可能性があることを考慮する必要があります。2 つのファイルabを作成すると、OS に渡したファイル名とファイル名が異なる1 つのファイルcまたは 2 つのファイルdeが作成される場合があります。外部ラッパー ライブラリまたは多数の が必要です#ifdef
  • 特にポータブルにしたい場合は特に、エンコーディングの不可知性は実際には機能しません。wchar_tこれは Windows では UTF-16 コード単位でありchar、Linux では多くの場合 (常にではありません) UTF-8 コード単位であることを知っておく必要があります。多くの場合、エンコーディングの認識はより望ましい目標です。どのエンコーディングを使用しているかを常に把握するか、エンコーディングを抽象化するラッパー ライブラリを使用するようにしてください。

追加のライブラリとシステム固有の拡張機能を使用し、それに多くの労力を費やさない限り、移植可能な Unicode 対応アプリケーションを C または C++ で構築することは完全に不可能であると結論付けなければならないと思います。残念ながら、ほとんどのアプリケーションは、「ギリシャ文字をコンソールに書き込む」や「システムが許可する任意のファイル名を正しい方法でサポートする」などの比較的単純なタスクで既に失敗しており、そのようなタスクは真の Unicode サポートへの最初の小さなステップにすぎません。

于 2011-06-11T21:18:07.560 に答える
9

wchar_tプラットフォームに依存するため(定義により「シリアル化可能」ではない)、タイプを避けます。WindowsではUTF-16、ほとんどのUnixライクなシステムではUTF-32です。代わりに、C++0x/C1xのchar16_tand/or型を使用してください。char32_t(新しいコンパイラがない場合は、今のところ型定義してくださいuint16_tuint32_t)

DOは、UTF-8、UTF-16、および UTF-32 関数間で変換する関数を定義します。

Windows API が -A および -W で行ったように、すべての文字列関数のオーバーロードされたナロー/ワイド バージョンを記述しないでください。内部で使用する優先エンコーディングを1 つ選択し、それに固執します。別のエンコーディングが必要なものについては、必要に応じて変換してください。

于 2011-06-10T01:03:18.740 に答える
8

の問題wchar_tは、エンコーディングにとらわれないテキスト処理が難しすぎて避けるべきだということです。おっしゃる通り「純粋な C」に固執すれば、や フレンズのw*ようなすべての機能を使用できますwcscatが、より高度なことをしたい場合は、深淵に飛び込む必要があります。

wchar_tUTFエンコーディングの1つを選択した場合よりもはるかに難しいことがいくつかあります。

  • Javascript の解析: 識別子には、BMP 以外の特定の文字を含めることができます (この種の正確性に関心があると仮定しましょう)。

  • HTML:&#65536;の文字列にどのように変換しますwchar_tか?

  • テキスト エディター: 文字列内の書記素クラスター境界をどのように見つけますwchar_tか?

文字列のエンコーディングがわかっていれば、文字を直接調べることができます。エンコーディングがわからない場合は、文字列でやりたいことはすべて、どこかのライブラリ関数によって実装されることを期待する必要があります。したがって、特に有用なデータ型wchar_tとは考えていないため、 の移植性はあまり関係ありません。

プログラムの要件は異なる場合があり、適切にwchar_t機能する場合があります。

于 2011-06-11T11:35:05.917 に答える
6

それiconvが「純粋な標準 C/C++」ではないことを考えると、独自の仕様を満たしているとは思えません。

新しいcodecvtファセットが付属しchar32_tchar16_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 (同上) のようなトリッキーなものから標準化されていると思いcodecvt_utf16ます。

実際、私は上記をできるだけ短く書きましたが、実際には次のようなヘルパーが必要です。

template<typename... T>
inline ns::string
ns::from_interface0(T&&... t)
{
    return converter0().from_bytes(std::forward<T>(t)...);
}

これにより、各メンバーの 3 つのオーバーロードにアクセスでき、 egや range[from|to]_bytesなどを受け入れます。const char*

于 2011-06-10T01:37:49.580 に答える