質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

12681閲覧

C++ : std::wifstreamってなんのためにあるのでしょうか?

Chironian

総合スコア23272

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

4グッド

2クリップ

投稿2017/05/02 09:48

編集2017/05/02 09:51

std::wifstreamの機能を完全に勘違いしてました。
ファイルからの読み出しをwchar_t単位で行うものと思ってたのです。
しかし、operator>>で読み出した時、デフォルトでは1char単位で読み出してました。1charを1wchar_tへ読み出しているのです。
また、コンソールに日本語をキレイに出力したいの「解決した方法」でaglkjgggさんが書かれている内容から、指定された文字コード変換を行ってwchar_tへ読み込んでいるようです。つまり、デフォルトの文字コード変換はASCII→UTF-16と思われます。

しかし、そのような機能であれば、std::ifstreamoperator>>(wchar_t*)等を実装した方が使い勝手が良いように感じます。

なぜ、現在のような仕様になっているのか、推測できる方いらっしゃいませんか?

Fritha, kyunta, aglkjggg, can110👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

ファイルからの読み出しをwchar_t単位で行うものと思ってたのです。

つまり、デフォルトの文字コード変換はASCII→UTF-16と思われます。

厳密にはASCIIコードではなく、ロケールに従ったエンコード方式(コードページ)でファイルを読み、ワイド文字(WindowsならUTF-16、LinuxならUTF-32)に変換するという仕様ですね。初期状態ではC/C++のロケールは"C"に設定されているのでASCIIコードとなります。
「テキストファイルをワイド文字で読み書きする」という機能を実現するなら、C++ではワイド文字の文字コードが規定されていないのでワイド文字を入出力に使うわけにはいきませんから、ロケールに従って変換する、という使い方は合理的だと思います。

ちなみに、C標準のsetlocale関数ではC++ライブラリ側のロケールが設定されないので、std::locale::global関数を使用する必要があります。

C++

1std::locale::global(std::locale(""));

「コンソールに日本語をキレイに出力したい」の方をちらっと見ましたが、setlocaleを使っているのが問題のような気がします(すでに解決済みですが)。

投稿2017/05/02 14:44

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Chironian

2017/05/02 15:34

回答ありがとうございます。 あっとそうでした。LinuxだとwstringはUTF-32ですね。 文字コード変換すること自体は(ちゃんと実装されていれば)有り難い機能ですので、規格がそれを規定していることはたいへんありがたいと思います。 そして、この変換は当然std::ifstreamでも通用するはずです。(basic_streamの実体化に過ぎませんので) このテンプレート・パラメータのchar_typeが、operator>>とreadのchar_typeを決めているだけのように見えます。これだけなら、std::ifstreamでオーバーロードすれば十分じゃないのか?と感じます。 しかし、わざわざ複雑にしているのには、何か理由があるのではないかと思うのです。 その理由について何か思い当たらないでしょうか?
catsforepaw

2017/05/02 16:04

> しかし、わざわざ複雑にしているのには、何か理由があるのではないかと思うのです。 その理由について何か思い当たらないでしょうか? ANSI文字列とワイド文字列を混在して使うというのはかなりレアなケースだと思います。例えば、Windows SDKのAPI関数にはANSI版とワイド版の両方が提供されていて、ANSI版にはANSI文字を、ワイド版にはワイド文字を渡すようになっているのは言うまでもありませんが、それで困る状況は思いつきません。 もしifstreamにワイド文字への変換機能を搭載するとなると、basic_streamのテンプレート引数に渡す文字型は二つ必要ということになり、実装が複雑になりすぎるような気がします。 つまり、レアなケースを想定して「わざわざ複雑な実装」とするよりは、そういうケースは個別に自分で考えてね、というスタンスなのだと理解しています。
Chironian

2017/05/02 18:00

> basic_streamのテンプレート引数に渡す文字型は二つ必要ということになり たぶん、私がbasic_streamの理解が浅いせいと思うのですが、何故2つ必要になるのでしょうか? そもそも、char_typeがテンプレート・パラメータである理由が実は良く分かっていません。 1.ファイル側の文字サイズは指定したエンコードで変わるのでテンプレート・パラメータで指定するものではない。 2.operator>>等のサイズもオーバーロードで指定できるのでテンプレート・バラメータで指定する必要が分からない。 operator>>の文字用は非メンバー関数でなので、そもそもテンプレート・パラメータではなかったりします。(readなどはメンバ関数なのでそうでもないですけど、そもそもread(wchar_t*)って意味あるのでしょうか?) という感じです。
catsforepaw

2017/05/03 01:14

> たぶん、私がbasic_streamの理解が浅いせいと思うのですが、何故2つ必要になるのでしょうか? よく考えたらテンプレート引数にする必要はなかったですね。訂正します。その代わり、char_traitsにエンコード変換に関する情報や操作を追加する必要が出るかもしれません。 > そもそも、char_typeがテンプレート・パラメータである理由が実は良く分かっていません。 C++の`stream`は基本テキストファイルを想定しているので、文字列と密接に関連しています。そうなると「どの文字コードを扱うのか」を明確にするためには、やはりテンプレートで指定する必要はあるかと思います。 厳密にUnicodeに対応したコードを書くために、char16_tやchar32_tを使いたくなるかもしれませんし(char8_tを規格に入れなかったのは失敗だと思う)。 > 1.ファイル側の文字サイズは指定したエンコードで変わるのでテンプレート・パラメータで指定するものではない。 厳密には`wifstream`のときはロケールに応じたエンコードで読んでワイド文字列に変換しますが、`istream`ではロケールを無視してバイト列をそのまま読み込みます(設定したロケールは、当然のことながら`char`にも反映されるため)。テンプレート引数で渡した文字コードによってファイル読み込みの挙動が変わります。 > 2.operator>>等のサイズもオーバーロードで指定できるのでテンプレート・バラメータで指定する必要が分からない。 > operator>>の文字用は非メンバー関数でなので、そもそもテンプレート・パラメータではなかったりします。(readなどはメンバ関数なのでそうでもないですけど、そもそもread(wchar_t*)って意味あるのでしょうか?) 前述の通り、文字コードに応じてファイル読み込みの挙動が変わるため、たとえ非メンバ関数でも、その実装ではメンバ関数を呼ぶ必要があると考えます。 C++にストリームが実装されたのはかなり昔のことなので、考え方が古めかしいのはしかたがないのかもしれません。C#のようにファイル入出力(Stream)とエンコード処理(TextReader/Writer)に分離して考えればすっきりするのですけどね。
Chironian

2017/05/03 04:07 編集

> char_traitsにエンコード変換に関する情報や操作を追加する必要が出るかもしれません。 実は、私はchar_traitsを指定する意味も良く分かってないです。 デバイスとのやり取りの型を指定しているわけではないから、wchar_tについてEOF云々を戻しても仕方が無い気がします。文字列比較をifstreamが使う場面も良く分からないですし。 https://cpprefjp.github.io/reference/string/char_traits.html > 「どの文字コードを扱うのか」を明確にするために これはlocaleで厳密に与えることができるのでlocaleで十分なように感じます。 むしろlocaleで動的に与えることができる以上、動的に切り替える仕組みの方がスマートなように思います。 > (char8_tを規格に入れなかったのは失敗だと思う) ほんとそう思います。 > `istream`ではロケールを無視してバイト列をそのまま読み込みます げっ、そうなんですか? たぶん、無視するのはグローバル・ロケールですよね? imbueで与えるロケールも無視するのですか? それが規格なら、Shift-JISファイルをUTF-8へエンコードして読み出すことをifstreamは規格上サポートできないということになってしまいます。(現在、これをサポートしている実装はないだろうとは思いますが、需要はある筈。) グローバル・ロケールは便利のための方便と思うので、最も良く使うifstream等への影響がないのは好ましいと思います。(グローバル・ロケールは影響範囲が分かりにくいので寧ろ無いほうが...) > 前述の通り、文字コードに応じてファイル読み込みの挙動が変わるため、たとえ非メンバ関数でも、その実装ではメンバ関数を呼ぶ必要があると考えます。 その通りですね。これは元のchar_typeとは無関係にI/Fを作れると言う例示のつもりでした。 > C++にストリームが実装されたのはかなり昔のことなので、考え方が古めかしいのはしかたがないのかもしれません。C#のようにファイル入出力(Stream)とエンコード処理(TextReader/Writer)に分離して考えればすっきりするのですけどね。 そうですよね。 C#のStreamに近い部分として、std::streambufがあるような印象をうけてます。(実はよく分かってませんが。)でも、std::streambufもchar_typeをテンプレート・パラメータに持つのですね。ということは、streambufとファイルとの間で文字コード変換しているということになりますね。なんだかな~。 (文字コード変換専用のバッファを別途必要としますから。) まだ文字コードについての経験が十分でなかった時代に策定されて、それとの互換性を考慮した結果、現在の仕様になっているというのが正解かも知れませんね。
catsforepaw

2017/05/03 04:07

> たぶん、無視するのはグローバル・ロケールですよね? imbueで与えるロケールも無視するのですか? すみません。そこまで試したことはないのですぐには答えられません。実のところ、エンコードを気にしないといけないようなファイル入出力では、私は標準ライブラリーは使わないことにしている(WindowsとLinuxではwchar_tの違いやロケール指定の違いで標準ライブラリーといえども共通化できないので、それなら最初から完璧に制御可能な外部ライブラリーを使った方が良い、というスタンスです)ので、使いこなせているわけではないのです。 > まだ文字コードについての経験が十分でなかった時代に策定されて、それとの互換性を考慮した結果、現在の仕様になっているというのが正解かも知れませんね。 そういう面はあるかと思います。多言語(複数文字エンコード)対応のコードを書くのはC++標準ライブラリーだけだとかなりしんどいですね。
Chironian

2017/05/03 04:28

> 多言語(複数文字エンコード)対応のコードを書くのはC++標準ライブラリーだけだとかなりしんどいですね。 確かにそうですね。 規格上は、Unicodeだけなら恐らくできますが、実装が対応していない(gccは頑なに対応しないし、msvcも中途半端だし)ので、現状では無理と思います。なので、私はboost::localeのUTF変換部分を使ってます。Shift-JISというかMultiByteはWindows限定と割り切りました。 結論としては、「std::ifstreamにstd::operator>>(wchar_t*)ではなくstd::wifstreamが規定されているが、そのメリットは特に見つからず、歴史的経緯でこうなった」ということで納得しました。 おつきあい、ありがとうございました。
catsforepaw

2017/05/03 04:38

私は多言語(多エンコード)処理では`ICU4C`というオープンソースのライブラリーを使っています。ライセンスはBSDのようにライセンス表示義務さえ守れば修正や配布など割と自由で商用利用も可能となっているので使いやすいと思います。
Chironian

2017/05/03 06:36

おお、やはり"ICU"(http://d.hatena.ne.jp/Goto_youthK/20121125/1353841899)でしたか。 私も多言語化対応を検討した時、検討しました。結局、開発中のTheolizer(C++用シリアライザ)で使うにはオーバースペックなので断念しました。 でも、多エンコード(良い表現ですね)が必要な時は第一候補と思います。
faithandbrave

2017/05/08 06:48 編集

補足ですが、ロケールと文字コードはあまり関係ないですよ。ロケールは、1. 数値の3桁ごとの桁区切り文字や小数点の文字、2. 国ごとに異なる日時のフォーマット、といったものを切り替えるためのもので、文字コードを変換するためのものではないです。(「あまり」と書いたのは、Unicode Datetime Formatとかがあるので、全く無関係ではない、くらいです) iostreamの設計が古いというのはその通りで、char_traitsもUnicodeを考慮して設計はされていません。 iostreamのwchar_t版はあまり現実に即しているように思えないので、char版を使用し、文字コード変換は自分であとでするのがよいと思います。 くわしく設計論拠を知りたい場合は、WG21のドキュメントを遡って以下のような文章を読み解くか、 N0470 A Draft for the Specification of the IOStream Classes http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1994/N0470.asc 『Standard C++ IOStreams and Locales: Advanced Programmer's Guide and Reference』 https://www.amazon.co.jp/dp/0321585585/ のような書籍を読むことである程度は調べられますが、あまり納得できる根拠は得られないと思います。 wchar_tも日本のワーキンググループから提案して入ったものですが、文字コードの理解がそこまで広がっていなかったせいか、ライブラリも含めてそこまで詰めた仕様にはなっていません。
Chironian

2017/05/08 10:04

faithandbraveさん、コメントありがとうございます。 > ロケールと文字コードはあまり関係ないですよ。 なるほど。ロケール=地域ですから本質的には無関係であること納得できます。 ただ、実装上はlocaleにcodecvtを指定して文字コード変換する等、密接に関係しているように思います。 国ごとの表記の習慣を吸収するロケール層→文字コード変換する層→入出力層 のように3段階の層がありますが、文字コード変換もロケールに入れてしまったのが現在の実装のように感じます。 UTF-8がなかった時代に設計された仕組みなので、そうせざる得なかったのだろうと思います。 > iostreamの設計が古いというのはその通りで、char_traitsもUnicodeを考慮して設計はされていません。 やはりそうでしたか。自分の理解が間違っていないようでほっとします。 > iostreamのwchar_t版はあまり現実に即しているように思えないので、char版を使用し、文字コード変換は自分であとでするのがよいと思います。 ですね。デフォルトはUTF-8一択で、どうしても日本語を効率よく扱わないと行けないアプリのみワイド文字を使い、かつ、iostreamとやり取りする前にUTF-8へ変換する使い方が良いと私も感じます。 wifstreamの設計が本質的になんか変と感じました。そのような時の多くは私が重要なことを把握できていない場合が多いので質問をたてました。が、おかげさまで「なんか変」が事実であることが確認できましたので、これで十分です。ありがとうございました。
guest

0

回答ではありません。すみません。
もやもやしたままですが、まずはとっかかりとして以下は参考になるのかなと思いました。

does (w)ifstream support different encodings

ざっと目を通したのですが、結局、以下の言葉に従うのもアリかなと思いました。
Pythonの設計哲学

環境に戦いを挑むな。そして流れに身を任せよ。

ここでは環境=標準入出力の接続先=シェル/ターミナルと解釈しました。
詳しい方の回答を待ちたいと思います。

投稿2017/05/02 13:21

can110

総合スコア38266

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Chironian

2017/05/02 13:46

コメント、ありがとうございます。 Stackoverflowの記事は文字エンコード変換方法について議論しているように見えました。 「なぜ、std::ifstreamにstd::operator>>(wchar_t*)を規定するのではなく、std::wifstreamを規定したのか?」への繋がりが読み取れませんでした。 この辺の実装は本当に置いてきぼりされているので、あまり密に議論されてないだけかも?と少し感じてます。(gccは確かunicode変換さえサポートしてなかったと思います。msvcでもUTF-32との変換はサポートしてないようでした。) > 環境に戦いを挑むな。そして流れに身を任せよ。 標準規格に戦いを挑む意図はないです。しかし、規格の仕様の主旨を理解することは結構重要と考えています。そして、自分の目でみておかしな仕様に見える時の多くは自分の理解が浅い時なのです。 でも、 もしも、誰もメリットを推測できないなら、ま、そこまでの仕様だったと言うことで、そのまま使うなり、便利スニペットを作ってみるなりすると思います。 例えば、Cの標準ライブラリに慣れている人はきっと皆そうやっているだろうと思います。
can110

2017/05/02 14:01

コメントありがとうございます。URLざっと読んだだけで丸投げしてすみません(笑 ただ、つまるところ言語(C++であれば標準規格)がどこまでをサポートしている(すべき)かという議論になろうかと思います。 たとえばstd::ifstreamにstd::operator>>(wchar_t*)を規定しても、ストリームの接続先(言語実装系の範囲外)が1byteしか返してくれない環境ではどうふるまうべきかが私には分かりません。 が、言語がどの範囲まではサポートしているのかは私も知りたいです。 リンク先の「Standard C++ IOStreams and Locales: Advanced Programmer's Guide and Reference」https://www.amazon.com/dp/0201183951/?tag=stackoverfl08-20 にはこの辺りも書かれているのではないかと推測しているのですが。
Chironian

2017/05/02 14:21

なるほど。 1バイトづつしか返さないデバイスも想定して通常はバッファリングしている筈です。 ですので、どちらの方法を使ってもその辺で問題はでないだろうと思います。 しかし、「Standard C++ IOStreams and Locales」だけで一冊の本がかけるのですね。 確かにそのくらい複雑な話だろうと思います。日本語だったら買って読むのですが、英語で読む根性がない自分が恨めしいです。
can110

2017/05/02 14:42

> 1バイトづつしか返さないデバイスも想定して通常はバッファリングしている筈です。 たしかにその通りだと思います。ただ、私も前回の質問でもやもやしていたところなのですが、ストリームの接続先(コマンドプロンプト)が勝手に入出力を調整(パディング)している可能性もあるのかなと。 たとえばC++側からcoutで1byteしかくれなかったから勝手に1byte足して出力(表示)していたりする可能性があるのかなと考えました。さらにこの動作はsetlocaleが作用しているのかと妄想していました。 chironianさんのそもそもの疑問からは外れているかもしれません。 ただ、私としては前回の質問でのsetlocaleがどこに作用しているのか(なぜ結果が異なるのか)が知りたいところです。 > 英語で読む根性がない自分が恨めしいです。 私もありませんw
Chironian

2017/05/03 03:30

たぶん、catsforepawさんの回答が参考になると思います。 > ちなみに、C標準のsetlocale関数ではC++ライブラリ側のロケールが設定されないので、std::locale::global関数を使用する必要があります。 C言語用の領域のみ設定した時、C++への影響が中途半端になっているということかも。
can110

2017/05/03 03:58

catsforepawさんとのやりとり、大変興味深く拝見してました。理解は追いついていないのですが。。 (Cでの)setlocale、std::locale::global、std::ios_base::imbue の違いと影響範囲についてちゃんと理解しないとだめですね。以下などを読みながら、確認してみようかと思います。 3.3 C 言語でのロケールと C++ ロケールとの違い https://docs.oracle.com/cd/E19205-01/820-2985/loc_io/3_3.html >C#のようにファイル入出力(Stream)とエンコード処理(TextReader/Writer)に分離して考えればすっきりするのですけどね。 には完全に同意です。 コメントありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問