|
本サイトは移転しました。旧アドレスからのリダイレクトは2025年03月31日(月)まで有効です。
|
🛈 | ✖ |
C++標準ライブラリのファイルストリームについて文字コード変換を中心に確認する。
ストリームとはデータ入出力を文字列データの流れ(ストリーム)として抽象化したオブジェクトである。
C++標準ライブラリはストリームとストリームバッファを区分し、ストリームは書式化、ストリームバッファはバッファリングを担当する。アプリケーションはストリームとデータ交換し、ストリームが関連付けられたストリームバッファとデータ交換する。ファイルストリームはファイルストリームバッファを内部に所有し、ファイルストリームバッファが最終的に物理ファイルとデータ交換する。
ファイルストリームにあずかる文字および文字列は内部(アプリケーションで処理するデータ)と外部(物理ファイルに保存されるデータ)で文字コードが一般に異なり、ファイルストリームバッファがその変換を担当する。ファイルストリーム、ファイルストリームバッファ共にクラステンプレートとして供給される(JTC1/SC22/WG21 N4659 30.9.1)。
テンプレート仮引数charTが内部で符号(文字を表現するデータ)を格納するデータ型(char、wchar_tなど)、traitsがcharTの特性定義クラス(N4659 24.2)である。traitsは符号や符号列の比較関数や操作関数などを定め(24.2.1/p1)、そのデフォルトはchar_traitsクラステンプレート(24.2)のcharTによる特殊化である。特にtraitsのtypedefメンバであるstate_type(traits::state_type)(24.2.2/p4)がファイルストリームの文字コード変換に重要な役割を担い、char_traits<charT>でこれはmbstate_t(24.2.3.1/p4)である。
ロケールは様々な言語への対応(ローカライゼーション)をカプセル化するもので、C++標準ライブラリはその目的でlocaleクラスを用意する(N4659 25.3.1)。localeは複数のファセットを持つ。ファセットはローカライゼーションの様々な側面を実装する複数のクラスあるいはクラステンプレートで、全てlocale::facetクラスを共通の基底とする。ファセットのほとんどは書式化を指定し、例えばnum_putファセットは数値を文字列に書式化する方法を指定する(25.4.2.2/p1)。従ってファセットのほとんどは書式化を担当するストリームが利用するもので、ストリームバッファとは無関係と言える。
文字コード変換ファセット(codecvtファセット)は異なる文字コード間の相互変換を行う(25.4.1.4/p1)。ファイルストリームバッファはcodecvtファセットを使って内部文字コード(アプリケーションが処理するデータ)と外部文字コード(物理ファイルに保存されるデータ)を相互変換する(30.9.2/p5)。codecvtファセットはストリームが直接利用しない例外的なファセットで、データ入出力においてファイルストリームバッファのみが利用する特殊な存在と言える。しかしながら、ストリームのロケール設定(imbue)は同時に関連付けられたストリームバッファのロケール設定(pubimbue)を行うため(30.5.5.3/p9)、codecvtファセットもストリームロケール設定による方法が多く採られる。そのためその特殊性が意識される事は少ない。
ctypeファセットはC言語標準ライブラリのctype.h(JTC1/SC22/WG14 N1570 7.4)とwctype.h(N1570 7.30)をカプセル化し(N4659 25.4.1.1/p1)、文字の分類やマッピングなどを行う。ctypeファセットとcodecvtファセットはctypeカテゴリに分類され(N4659 25.3.1.1/p2)、標準ライブラリはそれぞれについてctypeクラステンプレートとcodecvtクラステンプレートを定義する。
テンプレート仮引数charTは符号を格納するデータ型、internTはその内部型(ファイルストリームにおいてはアプリケーションが処理するデータ型)、externTは外部型(ファイルストリームにおいては物理ファイルに保存されるデータ型)である。stateTはとりあえず、標準がmbstate_tである事のみを意識しよう。このmbstate_tは既述したようにファイルストリームのデフォルトでtraits::state_typeにtypedefされている。
localeはctypeカテゴリに関し以下のファセットをデフォルトで所有する(N4659 25.3.1.1.1/p3)。ファイルストリームバッファは対応するcodecvtファセットを用いるが、codecvtファセットは派生クラスで置換することができる。
以下に規格定義動作(N4659 25.4.1.4/p3)と本サイトの使用するコンパイラmingw-w64実装動作を示す。codecvt<wchar_t,char,mbstate_t>の詳細はソースコード(mingw-w64の標準codecvt)に示す。なおC++20はchar8_tの追加でcodecvt<char16_t,char,mbstate_t>は非推奨となりcodecvt<char16_t,char8_t,mbstate_t>が代替となる。
codecvtファセット | 内部型 | 外部型 | 規格 | mingw-w64実装 | ファイルストリームバッファ |
---|---|---|---|---|---|
codecvt<char,char,mbstate_t> | char | char | 無変換 | 無変換 | filebuf |
codecvt<char16_t,char,mbstate_t> | char16_t | char | UTF-16⇔UTF-8 | UTF-16⇔UTF-8 | basic_filebuf<char16_t> |
codecvt<char32_t,char,mbstate_t> | char32_t | char | UTF-32⇔UTF-8 | UTF-32⇔UTF-8 | basic_filebuf<char32_t> |
codecvt<wchar_t,char,mbstate_t> | wchar_t | char | 実装依存 | setlocaleに従う | wfilebuf |
以下のサンプルコードは実装動作を確認する。ソースコードはUTF-8であるか、またはシフトJISで入力文字コードをCP932(-finput-charset=CP932)とする。出力ファイルはUTF-8である。wchar_t文字列はそのままでASCII以外は変換に失敗し失われ、setlocale(LC_CTYPE,"")すればシフトJISで出力して文字化けする。
ヘッダ<codecvt>はC++17(N4695 D.15)で非推奨となったcodecvtファセットを定義する(N4659 D.15.1)。これらは全てcodecvt<Elem,char,mbstate_t>クラスを継承する。
Elemをwchar_tとした場合を一覧し、mingw-w64デフォルト(ワイド実行文字コードがUTF-16)の実装動作を確認する。
codecvtファセット | 内部型 | 外部型 | 規格 | mingw-w64実装 | ファイルストリームバッファ |
---|---|---|---|---|---|
codecvt_utf8<wchar_t> | wchar_t | char | UCS-2/UCS-4⇔UTF-8 | UTF-16⇔UTF-8 | wfilebuf |
codecvt_utf16<wchar_t> | wchar_t | char | UCS-2/UCS-4⇔UTF-16BE | UTF-16⇔UTF-16BE | wfilebuf |
codecvt_utf16<wchar_t,0x10ffff,little_endian> | wchar_t | char | UCS-2/UCS-4⇔UTF-16LE | UTF-16⇔UTF-16LE | wfilebuf |
codecvt_utf8_utf16<wchar_t> | wchar_t | char | UTF-16⇔UTF-8 | UTF-16⇔UTF-8 | wfilebuf |
UCS-2/UCS-4はElemのサイズに依存しウィンドウズwchar_tは16ビットでUCS-2となる。mingw-w64はこれをいずれもUTF-16で実装する。これら全てcodecvt<wchar_t,char,mbstate_t>を継承し、wfilebufのcodecvtに置き換えることができる。これらを利用する事で、ユニコード文字列にwchar_tを使用するレガシーコードでもファイルストリーム出力を容易に行えたが、今や非推奨機能で将来に不安が残る。
codecvt_utf16は外部型charのバイト列で出力するので、UTF-16エンディアン定義が必要になる。BOMを先頭に付加するにはModeにgenerate_headerとconsume_headerを加える(N4659 D.15.2/p1)。codecvt_utf16のみファイルストリームをバイナリモードで開く必要がある(D.15.2/p3)。
標準で対応できない文字コード変換は自らcodecvtファセットを作成しカスタマイズするが、その方法は二つある。
通常localeの設定はファイルストリームバッファ(pubimbue)でなくファイルストリーム(imbue)を用いるため、以下においてファイルストリームへの設定として説明するが、実際はファイルストリームバッファへの設定である事を改めて強調する。ファイルストリームおよびファイルストリームバッファの符号格納データ型はcharTとする。なお詳細はソースコード(codecvtファセット)に譲る。
codecvtクラステンプレートの特殊化は以下を手順の概要とする。
ライブラリ標準codecvtファセットの置き換えは以下を手順の概要とする。