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に取り込むことも上とほとんど同様に出来ます.