19

正直なところ、C++Standardライブラリでは次の設計上の決定は得られません。ワイド文字をファイルに書き込む場合、文字にwofstream変換wchar_tされcharます。

#include <fstream>
#include <string>

int main()
{
    using namespace std;

    wstring someString = L"Hello StackOverflow!";
    wofstream file(L"Test.txt");

    file << someString; // the output file will consist of ASCII characters!
}

私はこれが標準と関係があることを知っていますcodecvtcodecvtにありutf8ますBoost。また、ここSOにはMartinYorkによるcodecvtforがあります。問題は、なぜワイド文字を変換するのかということです。そのまま文字を書いてみませんか!utf16standard codecvt

また、C ++ 0xで現実になるのでしょうunicode streamsか、それともここで何かが足りないのでしょうか。

4

5 に答える 5

13

最初の質問に対する非常に部分的な答え:ファイルバイトのシーケンスであるため、'sを処理する場合、との間でwchar_t少なくともある程度の変換が発生する必要があります。この変換を「インテリジェントに」行うには、文字エンコードの知識が必要です。そのため、ストリームのロケールでファセットを使用することにより、この変換をロケールに依存させることができます。wchar_tchar

次に、問題は、標準で必要とされる唯一のロケールである「クラシック」ロケールでその変換をどのように行うかです。そのための「正しい」答えはありません。したがって、標準はそれについて非常にあいまいです。あなたの質問から、wchar_t[]とchar[]の間で盲目的にキャスト(またはmemcpy()-ing)するのが良い方法だと思い込んでいることを理解しています。これは不合理ではなく、実際、一部の実装で行われている(または少なくとも行われた)ことです。

もう1つのPOVは、codecvtがロケールファセットであるため、「ロケールのエンコーディング」を使用して変換が行われることを期待するのが妥当です(概念がかなりあいまいなので、ここでは手に負えません)。たとえば、トルコ語のロケールではISO-8859-9を使用し、日本語のロケールではShiftJISを使用することが期待されます。同様に、「クラシック」ロケールはこの「ロケールのエンコーディング」に変換されます。どうやら、Microsoftは単純にトリミングすることを選択しました(UTF-16を表し、基本的な多言語面にとどまると仮定すると、IS-8859-1にwchar_tなります)が、私が知っているLinuxの実装はASCIIに固執することにしました。

2番目の質問:

また、C ++ 0xで実際のUnicodeストリームを取得するのでしょうか、それともここで何かが足りないのでしょうか。

n2857(私が手元にある最新のC ++ 0xドラフト)の[locale.codecvt]セクションでは、次のように読むことができます。

スペシャcodecvt<char16_t, char, mbstate_t>ライゼーションはUTF-16とUTF-8エンコーディングスキームcodecvt <char32_t, char, mbstate_t>間で変換され、スペシャライゼーションはUTF-32とUTF-8エンコーディングスキーム間で変換されます。codecvt<wchar_t,char,mbstate_t>ナロー文字とワイド文字のネイティブ文字セット間で変換します。

[locale.stdcvt]セクションには、次のものがあります。

ファセットの場合codecvt_utf8:—ファセットは、プログラム内でUTF-8マルチバイトシーケンスとUCS2またはUCS4(Elemのサイズに応じて)の間で変換する必要があります。[...]

ファセットの場合codecvt_utf16:—ファセットは、プログラム内でUTF-16マルチバイトシーケンスとUCS2またはUCS4(Elemのサイズに応じて)の間で変換する必要があります。[...]

ファセットの場合codecvt_utf8_utf16:—ファセットは、プログラム内でUTF-8マルチバイトシーケンスとUTF-16(1つまたは2つの16ビットコード)の間で変換する必要があります。

したがって、これは「はい」を意味すると思いますが、確実に「実際のUnicodeストリーム」が何を意味するかについてより正確にする必要があります。

于 2009-10-02T13:21:05.703 に答える
7

C ++が文字セットに使用するモデルはCから継承されているため、少なくとも1989年までさかのぼります。

2つの主なポイント:

  • IOはcharの観点から行われます。
  • シリアル化される文字の幅を決定するのはロケールの仕事です
  • デフォルトのロケール(「C」という名前)はごくわずかです(標準の制約を覚えていません。ここでは、7ビットASCIIのみをナロー文字セットとワイド文字セットとして処理できます)。
  • 「」という名前の環境によって決定されたロケールがあります

したがって、何かを取得するには、ロケールを設定する必要があります。

簡単なプログラムを使えば

#include <locale>
#include <fstream>
#include <ostream>
#include <iostream>

int main()
{
    wchar_t c = 0x00FF;
    std::locale::global(std::locale(""));
    std::wofstream os("test.dat");
    os << c << std::endl;
    if (!os) {
        std::cout << "Output failed\n";
    }
}

環境ロケールを使用し、コード0x00FFのワイド文字をファイルに出力します。「C」ロケールを使用するように依頼すると、次のようになります。

$ env LC_ALL=C ./a.out
Output failed

ロケールはワイド文字を処理できず、IOが失敗したため、問題が通知されます。UTF-8ロケールを尋ねて実行すると、次のようになります。

$ env LC_ALL=en_US.utf8 ./a.out
$ od -t x1 test.dat
0000000 c3 bf 0a
0000003

(od -t x1は、16進数で表されたファイルをダンプするだけです)、まさにUTF-8でエンコードされたファイルに期待するものです。

于 2009-10-02T15:10:10.237 に答える
3

wofstreamについてはわかりません。ただし、C ++ 0xには、UTF-8、UTF-16、およびUTF-32で移植可能に使用できる、保証された幅と符号付き(unsigned)の新しいdistict文字タイプ(char16_t、char32_t)が含まれます。さらに、新しい文字列リテラルがあります(たとえば、UTF-16でコード化された文字列リテラルの場合はu "Hello!")。

最新のC++0xドラフト(N2960)をチェックしてください。

于 2009-10-02T13:22:42.330 に答える
2

あなたの最初の質問については、これは私の推測です。

IOStreamsライブラリは、エンコーディングに関するいくつかの前提の下で構築されました。たとえば、Unicodeと他のあまり一般的ではないエンコーディングとの間の変換では、次のように想定されています。

  • プログラム内では、(固定幅の)ワイド文字エンコーディングを使用する必要があります。
  • 外部ストレージのみが(可変幅)マルチバイトエンコーディングを使用する必要があります。

これが、std::codecvtの2つのテンプレート特殊化が存在する理由であると思います。1つはcharタイプ間でマップし(おそらくASCIIを使用しているだけです)、もう1つはwchar_t(プログラムの内部)とchar(外部デバイス)の間でマップします。したがって、マルチバイトエンコーディングへの変換を実行する必要がある場合は常に、バイトごとに実行する必要があります。マルチバイトエンコーディングとの間で各バイトを読み書きするときに、エンコーディング状態を処理するファセットを記述できることに注意してください。

このように考えると、C++標準の動作は理解できます。結局のところ、ワイド文字のASCIIエンコードされた文字列を使用しています(これがプラットフォームのデフォルトであり、ロケールを切り替えなかったと仮定します)。「自然な」変換は、各ワイド文字のASCII文字を通常の(この場合は1文字の)ASCII文字に変換することです。(変換は存在し、簡単です。)

ちなみに、あなたが知っているかどうかはわかりませんが、変換に対してnoconvを返すファセットを作成することでこれを回避できます。次に、ワイド文字を含むファイルを作成します。

于 2009-10-02T15:13:41.417 に答える
2

これをチェックしてください: クラスbasic_filebuf

pubsetbufを使用して、ワイド文字バッファーを設定することにより、デフォルトの動作を変更できます。これを行うと、出力はcharではなくwchar_tになります。

言い換えれば、あなたの例では、次のようになります。

wofstream file(L"Test.txt", ios_base::binary); //binary is important to set!  
wchar_t buffer[128];  
file.rdbuf()->pubsetbuf(buffer, 128);  
file.put(0xFEFF); //this is the BOM flag, UTF16 needs this, but mirosoft's UNICODE doesn't, so you can skip this line, if any.  
file << someString; // the output file will consist of unicode characters! without the call to pubsetbuf, the out file will be ANSI (current regional settings)  
于 2010-08-12T14:08:25.517 に答える