UTF-8Nを吐くワイドストリーム

1昨日ブログで書いたことをコードで示すとこんな感じになります.

#include <fstream>
#include <locale>

// Boost 1.32.0からBOOST_ROOT/boost/utf8_codecvt_facet.hppと
// BOOST_ROOT/libs/serialization/src/utf8_codecvt_facet.cppを
// 掘り出してきてカレントにコピーしてます.
// あとcppファイル中のutf8_codecvt_facet.hppへのincludeパスを書き換えてます.
#include "utf8_codecvt_facet.hpp"

int main()
{
  using namespace std;

  locale::global(locale(""));

  wofstream fs("test.txt");

  bool is_utf8 = true;

  if(is_utf8){
    fs.imbue(locale(fs.getloc(), new utf8_codecvt_facet_wchar_t));
  }

  // is_utf8がtrueならUTF-8Nで,falseなら環境のデフォルト日本語エンコードで
  // test.txtに書き込まれます.
  fs << L"こんにちは,世界!" << std::endl;
}

順に説明します.

locale::global(locale(""));

グローバルのロケールを設定しています.空文字列を指定するlocale("")は環境のデフォルトのロケールオブジェクトを生成します.localeのコンストラクタが受け付ける文字列は現在の標準規格では完全に実装依存です.唯一標準で定められているロケール設定文字列は"C"だけです.日本語ロケールを設定する文字列として多くの実装が受け付ける"japanese"も厳密には標準の規格外です.ここで指定している空文字列は「環境のデフォルトのロケールを設定しろ」という指定ですが,これを実装がどう処理するかは完全に実装に依存します.典型的には環境変数LANGを読みにいくそうですが・・・.

fs.imbue(locale(fs.getloc(), new utf8_codecvt_facet_wchar_t));

ios_base::getloc()はストリームに設定されているロケールオブジェクトの取得に利用されます.ストリームオブジェクトの生成時にデフォルトで設定されるロケールはグローバルロケールなので,このgetlocで取得してるのはグローバルロケール(locale("")で設定されるロケール)です.

locale(fs.getloc(), new utf8_codecvt_facet_wchar_t)

ロケールクラスの次のコンストラクタ呼び出しです.

locale::locale(locale const &l, facet *pf)

ロケールlのうちファセットpfに該当する部分だけを取り替えたロケールを生成します.
(ファセットとはロケールの様々な側面を分解した個々のパーツのことを指します.例えば文字コード変換・文字照合・通貨表現などがファセットとなります.)
utf8_codecvt_facet_wchar_tはstd::codecvtからの派生クラスでコード変換ファセットですので,このコンストラクタはデフォルトのロケールのうちコード変換の部分をutf8_codecvt_facet_wchar_tに置き換えたロケールを作成します.
そうして作成したロケールios_base::imbueでストリームに設定することにより,コード変換を内部で勝手にやってくれるストリームが出来ます.
一つ気になることとして

new utf8_codecvt_facet_wchar_t

というふうに,ファセットオブジェクトをnewしたまま放置しているように見えます.しかしこれはlocaleオブジェクトが削除されるときにdeleteされるという保証があります.より詳細に説明するとfacetオブジェクトは常に以下のような形のコンストラクタを持ちます.

facet::facet(size_t refs = 0)

このコンストラクタの引数を0(デフォルト)で指定した場合には,facetオブジェクトはそれを使用しているロケールオブジェクトが全て削除されたときにdeleteされます.なので,上のようなファセットオブジェクトのnewは気軽に行ってかまわないです.
逆にスタックオブジェクトとして作ったファセットなど,勝手にdeleteをかけられるとマズイオブジェクトは生成時に1を指定します.

utf8_codecvt_facet_wchar_t utf8_facet(1);
locale l(prev, &utf8_facet);

こうするとファセットに勝手にdeleteがかけられることはありません.ただし,この場合には当然ファセットオブジェクトの生存期間がそれを使用しているロケールオブジェクトの生存期間を覆わなければなりません.その管理はユーザに任されます.そういう管理が面倒ならnewするのが一番簡単でしょう.ちなみにファセットのコンストラクタで指定しているこの数字,要するに参照カウントです.
そうして,fsにutf8_codecvt_facet_wchar_tを設定していると

fs << L"こんにちは,世界!" << std::endl;

では,ワイド文字列L"こんにちは,世界!"をfsに出力する際,fsは内部で自動的にutf8_codecvt_facet_wchar_t::outを呼び出して,ワイド文字列をUTF-8N(BOM無しのUTF-8)に変換したストリームをファイルに吐いてくれます.
逆に言うと,恐らくデフォルトの日本語ロケールにはワイド文字列をShift_JISEUC-JPに変換するcodecvtが標準で設定されているのでしょう.(locale::global(locale(""))やlocale::global(locale("japanese"))はそうしたコード変換ファセットを持ったロケールを設定していることになるんでしょう)
あと,utf8_codecvt_facet_wchar_tはUTF-8(N)のMBCSをワイド文字列(UCS-4)に変換する方のメソッドも当然用意していますので,UTF-8(N)で書かれたファイルをワイドストリームを通してwchar_tに取り込むことも上とほとんど同様に出来ます.

自分でstd::codecvtを拡張する

上で用いているutf8_codecvt_facet_wchar_tはstd::codecvtの一実装に過ぎません.ユーザはstd::codecvtからクラスを派生させて独自のコード変換ファセットを定義することが可能です.もちろん,そうして作成したファセットを上とまったく同様にストリームに組み込んで,ユーザが望むコード変換を内部に備えたストリームを作成可能です.
上で使っているutf8_codecvt_facet_wchar_tの実装を見ればおおよそ拡張の仕方が分かると思いますが,とりあえず簡単にだけ説明しておきます.
std::codecvtのpublicメンバであるinやoutなどは全て非仮想関数として定義されています.が,これらのメンバ関数は実装をそのまんま対応するprotectedな仮想関数であるdo_inやdo_outへ委譲しています.従って,std::codecvtからクラスを派生させて,そのクラスにprotectedなdo_inやdo_outといったメソッドを実装すれば,望むコード変換ファセットが実装可能です.これでoperator<<やoperator>>の呼び出しの際に任意のコード変換を行ってくれるストリームが作れます.
おしまい.
#自分で変換コード書くのは面倒だから,iconvあたりに丸投げするcodecvt作ればそれだけで・・・うひょひょ.<でも(GNUの)iconvは好きじゃない人

ロケール,ロケール,ロケール!

Collation周りも超面白そうな予感.見てる限りいろんなこと出来そう.

std::vector<std::wstring> str_vec;
.....
// ワイド文字列をShift_JIS文字列としてソートする
std::sort(str_vec.begin(), str_vec.end(), ucs_as_sjis_loc);

こんなんとか出来そうに読める.うふ,うふふ,うふふふふ・・・.

Boost.Regex + Locale

href="http://cvs.sourceforge.net/viewcvs.py/*checkout*/boost/boost/libs/regex/Attic/traits_class_ref.htm?rev=1.11
うふ.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1429.htm#traits_discussion
うふふ.
うふふふふ.
うふふふふふふ.
うふふふふふふふふ.
うふふふふふふふふふふ.
さすがだよ.
用意していないはず無いよ.
上の書いててふと気が付いたんだよ.
で,確かめてみたんだよ.
ちゃんとあるじゃない.
regex_traitsクラスなんてものが.
その中にもちろん無いはずがない.
locale_type
もし自分の勘が正しければ,出来るはず.
「ワイド文字列でマッチングさせながらShift_JISの範囲指定で取る」なんて芸当が.
うひ.
うひひ.
うひひひひ.