VC8のfilebufとsetlocale
今日はライブラリの作業を進める気分ではなかったので、前々から気になっていた、VC8のstd::filebufがstd::setlocale()を呼ばないと動かない問題と、std::setlocale()を呼ぶと標準出力/エラー出力がおかしくなる問題について調べてみました。
原因に関してはこんな感じでした。
std::filebufのファイル名がCのロケールに従ってワイド文字に変換される
↓
Cのロケールを変更するためにstd::setlocale()を呼ぶことになる
↓
Cのロケールが"C"以外になると、ファイルポインタへの書き込みで、ワイド文字経由でコンソールのコードページへの変換が発生する
↓
既定ではstdoutやstderrがバッファリングなしの設定なので、マルチバイト文字のリーディングバイトだけでワイド文字に変換しようとして失敗する
↓
書き込みに失敗したため、std::filebufがbad状態になり、その後の書き込みがすべて失敗する
対処法ですが、移植性を気にしなくてよければstd::filebufのワイド文字列版コンストラクタを使い、CロケールとC++グローバルロケールは変更しないのが簡単です。
この場合、個々のストリームのロケールは自由に変更して問題ありません。
ワイド文字列版を使いたくなければ、CストリームかC++ストリームのどちらかにバッファを設定しないといけません。
例えば、こんな感じになります。
#include <cassert> #include <clocale> #include <fstream> #include <iostream> #include <locale> #include <vector> int main() { typedef std::codecvt<wchar_t,char,std::mbstate_t> cvt_type; std::locale loc(""); const cvt_type& cvt = std::use_facet<cvt_type>(loc); // 多分、このサイズで大丈夫 std::vector<char> cout_buf(cvt.max_length()); std::cout.rdbuf()->pubsetbuf(&cout_buf[0], cout_buf.size()); // ofstreamのため必要 std::setlocale(LC_ALL, ""); std::ofstream os("日本語.txt"); assert(!!os); // コンソールのコードページと同じなので"C"のままでも書ける std::cout << std::cout.rdbuf()->getloc().name() << std::endl; std::cout << "こんにちは、世界!" << std::endl; // ロケールを設定してもOK std::cout.imbue(loc); std::cout << std::cout.rdbuf()->getloc().name() << std::endl; std::cout << "こんにちは、世界!" << std::endl; std::cout.imbue(std::locale::classic()); std::setlocale(LC_ALL, "C"); // 念のため、バッファを戻しておく std::cout.rdbuf()->pubsetbuf(0, 0); }
出力結果はこうなりました。
C
こんにちは、世界!
Japanese_Japan.932
こんにちは、世界!
まだ、ワイド文字絡みで曖昧な部分が残っていますが、これまで謎だった部分は大体解明できました。
これがVC8のバグかと言われると微妙な気がしますね。