c++で日本語の処理(ロケール周り) 7/8追記
またもや問題が発生したので…
この辺りのページが参考になりました。ありがとうございます。
http://0xc000013a.blog96.fc2.com/blog-entry-226.html
http://tkmakwins15.tuzikaze.com/contents/winapicpp4.htm
■問題
#include <iostream> #include <fstream> #include <string> #include <vector> #include <locale> using namespace std; void hoge(){ cout << "aiueo " << endl; cout << 12345<<endl; cout << "あいうえお" << endl; wcout << L"waiueo" << endl; wcout << 12345<<endl; wcout << L"あいうえお" << endl; } void read(){ wstring wstr; wifstream fin("hoge.txt"); while(fin >> wstr){ wcout << wstr << endl; } } int main (int argc, char * argv[]) { setlocale( LC_CTYPE, "ja_JP.UTF-8"); read(); hoge(); }
以上のコードが、ローカル(Macのターミナル上)だと上手くいっていたのだが、研究室のサーバー上では以下のエラーが出た。
terminate called after throwing an instance of 'std::ios_base::failure' what(): basic_filebuf::underflow invalid byte sequence in file Aborted
read()のwifstreamの部分でエラーを吐いてるよう。
ローカルのg++のバージョンは4.6.2、サーバー上だと4.5.2。バージョンの違いが問題?
■解決策1
setlocaleが怪しかったので、とりあえず以下のコードにしてみました。
一応上手くいったのですが、hoge()のcout部分が出力されなくなってしまった。
read()をコメントアウトするとcoutが出力されるけど、L"あいうえお"の出力がバグったりと、よくわからない挙動。
更に言えば、ローカルだと以下のコードでエラーが出る…なんぞ
void hoge(){ ~~ } void read(){ ~~ } int main (int argc, char * argv[]) { //setlocale( LC_CTYPE, "ja_JP.UTF-8"); std::locale::global(std::locale("")); read(); hoge(); }
■解決策2
imbueを使ってやる必要があるらしい?以下、上の参考URLからの引用。
グローバルなロケールを設定する場合、std::localeクラスのstaticメンバ関数のglobalを用いて設定します。
std::locale std::locale::global(const std::locale&);
これにロケールクラスのインスタンスを引き渡します。ただし、このロケールが影響を与えるのは、これを実行した後で生成したインスタンスに対してだけです。
たとえば、std::wcoutには影響を及ぼしません。そこで、std::basic_ostreamクラスの基底クラスであるstd::iosクラスのimbueメンバ関数を呼びましょう。
std::ios::imbue(const std::locale&);
これにlocaleクラスのオブジェクトを渡せばいいです。
Cであれば、setlocale()関数によって設定します。これはプログラム全体に影響を与えます。ロケール文字列は同じです。
ちなみに、std::locale::global()を実行すると、setlocaleを呼び出したのと同じ状態になります。つまり、Cライブラリのために、改めて設定する必要はありません。(C++規格上は、ですが…)
しかし、単にimbueを使ってもうまく行かず…。
先輩や准教にお聞きしてみた結果、なにやら下のコードでうまくいくとのこと。
int main (int argc, char * argv[]) { //setlocale( LC_CTYPE, "ja_JP.UTF-8"); std::ios_base::sync_with_stdio(false); std::locale::global(std::locale("")); std::wcout.imbue(std::locale("")); //std::wcout.imbue(std::locale("Japanese", std::locale::ctype)); //本当はこっち? std::wcin.imbue(std::locale("")); read(); hoge(); }
sync_with_wtdio(false)が重要なようです。C++の入出力とCの入出力の同期をとるかどうかを設定しているみたいですが…え、関係あるの?
そして、この状態だとwcoutの出力がカンマ区切りになります。
wcout << 12345<<endl; // 12,345と出力される
これはロケールをまとめて設定しているためで、本当はLC_CTYPE(文字処理関数)だけを設定する必要があります。
そのためのコードがコメントアウトしたほうのwcout.imbueなのですが…こちらを実行すると「no matching function for call」と怒られます。ぼぼぼ…
このあたりで、面倒になって放置しました。global設定してwcoutだけ使うことにすれば一応問題はないし。先輩達が分からないと言ってる時点で無理ゲーな気がする
ということで、どなたか知ってる方がいればお願いします。
■7/8追記
コメントを頂いたので追記します。ありがとうございます!
以下で上手くいきました。
using namespace std; int main (int argc, char * argv[]) { ios_base::sync_with_stdio(false); locale default_loc(""); locale::global(default_loc); locale ctype_default(locale::classic(), default_loc, locale::ctype); //※ wcout.imbue(ctype_default); wcin.imbue(ctype_default); // std::wcerrやstd::wclogにも必要に応じてimbueする read(); hoge(); }
※の行のコンストラクタでは、「std::locale::classic()をベースとして、ただしctypeだけdefault_locとする」という意味になります。(コメントそのままですいません)
こうやって引数を渡してやればよかったのですね。なるほど。
コメントを頂いたegtra様、ありがとうございました。
■更に追記
ローカルとサーバーの環境の違いでエラーが出る件について詳しく書いていなかったので、もうちょっと詳しく書きます。
前回に記事を書いてからサーバー上のg++のバージョンを上げてしまったので、上で書いてることとちょっと違います(´・ω・`)
サーバー:Ubuntu 11.10のg++ 4.6.1
ローカル:Mac 10.7.3のg++ 4.6.2
まず、一番最初のコード(setlocaleのみした時)について。
ローカルだと問題なく動作します。
サーバー上だと、エラーは起きない(g++のバージョンが上がったため?)ものの、出力は理想と違います。
$ ./a.out
(hoge.txtの一行目のみ。二行目以降表示されず)
waiueo
12345
あいうえお
最後のコード(追記したもの)について。
サーバー上だと問題なく動作します。
ローカルだと
terminate called after throwing an instance of 'std::runtime_error' what(): locale::facet::_S_create_c_locale name not valid Abort trap: 6
という例外がおきます。
以下のサイトでも同じ問題が書かれてたんですが、よく理解できませんでした(白目
http://d.hatena.ne.jp/whitypig/20101202/1291293537
http://d.hatena.ne.jp/eagletmt/20090208
とりあえず、setlocale使っとけ、という結論らしいです。
無知過ぎてやばい…