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

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

新規登録して質問してみよう
ただいま回答率
85.48%
プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

C++

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

Q&A

解決済

5回答

4227閲覧

関数に渡すストリームの入出力方向について

Chironian

総合スコア23272

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

C++

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

0グッド

0クリップ

投稿2016/12/10 02:42

関数の引数の入出力は明確にした方がソースを読みやすくなります。
そして、関数にストリームを渡すことも多いです。その入出力をどのように書くのが分かり易いのか、ちょっと悩ましいです。

下記のような説明はそれほど違和感はないと思います。

C++

1void WriteByAPI 2( 3 int iFd, // (IN )出力先ファイルのハンドル 4 DataClass const& iData // (IN )出力データ 5);

その延長で私は下記のように書くことが多いです。

C++

1void Write 2( 3 std::ostream iOStream, // (IN )出力先ストリーム 4 DataClass const& iData // (IN )出力データ 5);

しかし、iOStreamは出力先だから入力は可笑しいという議論があります。

上記2つのケースは意味的には同じですし、出力先ですのでiFdとiOStreamは出力と説明した方が良い筈ですが、どうも強い抵抗を感じます。
「ファイル・ハンドルやストリーム」を受け取るのではなく渡しているのだから、入力であると感じている自分がいます。

上記2つのケースがどちらとも存在しているプロジェクトについて、iFd, iOStreamを「入力と説明する」、「出力と説明する」、「入出力と説明する」、「入出力について述べない」等、何が一番可読性を上げることができるでしょうか?

感性の問題でもあると思いますので、確定的な答えは出ないような気もします。
ですので、皆さんのご意見を下さい。

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

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

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

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

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

guest

回答5

0

ベストアンサー

関数に対する入出力を明記したいが、そこにストリームなどの入出力の話をいれると混乱すると言うことでしょうか…。

私は基本的に関数の引数には入力しか渡さず、出力は戻り値を使うという方針です。ただ、この方針を採用するには、その言語で、

  1. 複数の異なる型を入れることができる配列やタプルが作れる。
  2. 常に生データの値渡し以外で返せる。(GC付き言語で参照の値渡しになっている等)

ということができないと全てで実践するのは難しいです。Cは1.の条件が厳しく(関数毎に構造体作ればできないことは無いかも知れないけど)、C++はタプルがありますが、2.の条件が厳しく、うまく右辺値参照でムーブにするなりスマートポインタなりで工夫しないと大きなデータの値渡しになって速度が落ちる場合があります。

ですので、そういったことが無理な言語のときは、引数に出力をつけるときだけ特別とみなすとしています。たとえば、成功失敗を戻り値に返すと同時に書き込んだバイト数も取得できるようにしたい場合に、Doxygenで説明を入れると次のような感じです。

C++

1/** 2 * データを指定の出力先に出力する。 3 * @param out 出力先ストリーム。 4 * @param data 出力されるデータ。 5 * @param count [out] カウントのポインタ。nullptr でなければ書き込んだバイト数が入る。 6 * @return 成功なら true、失敗なら false を返す。 7 */ 8bool Write(std::ostream out, DataClass const& iData, uint64_t *count);

関数への入力は特に説明しません。出力についてのみ、上の例の[out]のように統一的な何かを付けるなり、読めばわかるようなコメントにします。また、出力の場合は参照を使わずにポインタで渡すという謎規約が私の中にあるので、constが付いていないポインタは出力であることを前提としています。本当はC#のようにoutキーワードがあればもっとわかりやすいのですが…。

なお、変数名の頭に必ず"o"を付けるとか、そう言うことはしない方針です。

投稿2016/12/10 04:46

編集2016/12/10 11:42
raccy

総合スコア21735

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

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

Chironian

2016/12/10 05:49

回答、ありがとうございます。 > 関数に対する入出力を明記したいが、そこにストリームなどの入出力の話をいれると混乱すると言うことでしょうか…。 その通りです。 raccyさんの場合、出力ストリームに[out]を付けてないので入力っぽくも見えますが、白黒つけにくい場合があるので入力側は明記しない方針ということみたいですね。 > 出力についてのみ、上の例の[out]のように統一的な何かを付けるなり、読めばわかるようなコメントにします。 これと、oプリフィクスを付ける(仮引数名で出力であることを判別できるようにする)ことは同等の効果があると考えています。変数名や仮引数名はある意味コメントでもありますから。 そして、コメントよりは、エラーチェック機能が多少なりと働きます。 更に、設定している全ての部分で呼び出し元へ返却していることを意識できます。 更に更に、メンバ変数で同じ名前を使う時に悩まなくて済みます。 なので、私にとってはメリット盛りだくさんなのです。 こう考えると、ostreamとして受け取ったパラメータに値を設定しないので「出力」扱いは妥当ではない感じですね。 ならば、白黒付けないか、違和感あれど入力と書くって話かも知れません。 頭の中が少し整理できた気がします。 つまり、関数のパラメータの入出力は、その関数の呼び出し元とのI/Fの意味に従って決めるのが妥当ということと思います。 ならば、ostreamは関数から更に外への出力であり、関数の呼び出し元から「どこへ出力するのか?」の指示なので、関数I/Fのレベルでは入力で良いような気がしてきました。
raccy

2016/12/10 11:39

「関数への入力」という意味では出力先ストリームは関数にとって**入力**扱いです。「関数からの出力」の考え方はKSwordOfHasteさんと同じです(私は参照では無くてポインタ使いますが)。命令型プログラミングにおいて、引数で渡されたオブジェクトに副作用があることは**よくあること**なので、わざわざ区別しません。オブジェクトが変化することは出力では無く、入力された物に副作用が起きたという解釈です。置き換わる形でなければ、出力ではないと考えています。出力は、もし、パフォーマンスを無視すればタプルにして返すこともできないことはないものという感じです。 プリフィックスは…昔は付ける方が良いと考えていたんですが、付けないとわからないような関数は「途中で各変数の役目を忘れるような関数は行数が長すぎるのでは?」と思っています。エラーチェックは目視などに頼らず、変わらないことを保証したかったらconstを付けろよ、という方針です。
Chironian

2016/12/10 13:19

> 引数で渡されたオブジェクトに副作用があることは**よくあること**なので、わざわざ区別しません。 なるほど。副作用と捉えればすっきりしますね。了解です。お陰で思いが固まりました。 私の質問の例では出力ストリームはその関数への入力と捉えることにします。 プリフィクスの件は好みの問題もあるのでこの辺で。 でも、お陰様で自分の思いを明確にすることができました。ありがとうございました。
KSwordOfHaste

2016/12/10 14:02

C++を知ったばかりのころはよろこんでT&vを使ってoutパラメータにしたりしたのですが、なんだかraccyさんのルールの方が「呼び出し元で&を付けることでここに代入されるんだよ」がわかりやすくなる気がしました。何がよいルールなのかは難しいと改めて感じました。
haru666

2016/12/14 01:56

私も上記全てraccyさんと同じようにしています。 謎ルールは良いルールだと思ってますよ。ポインタ渡しであればデフォルト引数でnullにすることができますし、直観的にメインの処理で必要な情報を持たないということを表現できます。メインの処理で必要な情報の場合、毎回nullチェックをするような使い方をしないためには参照渡しである方が都合が良いからです。   void func(T *out) {   T status;   // 処理... outには途中でアクセスしない。   if (out) *out = status; }   void func(const T &in) {   // in にはそのままアクセスする。constが無ければinの情報が変わるという認識。 } // オーバーロードで引数省略版を用意。 void func() {   T default; // これをプロパティにしておくこともあります。   func(default); }
guest

0

個人的には関数引数に対するIN/OUTは以下のように考えることが多い気がします。

  1. 値渡しはIN、参照渡し(&v)かつvの内容が不定であってはならない場合もIN
  2. 参照渡し(&v)かつ変数自体の代入(v=...)が関数内で行われる可能性がある場合はOUT

引数のostreamあるいはCのstrcatの第一引数をINにするかIN/OUTにするかといったことや、C/C++の色々なライブラリーのドキュメント・コメント上IN/OUTがどういう意味で表記されているのか等々を考えると何がIN/OUTのよい基準なのか混沌としてくる気がします。

自分は「IN/OUTの表記は可読性を上げる目的には充分有用でない」と思います。ストリームのようなものは確かに「その引数は出力用」と考えたくなるのですが、ストリーム以外の色々なオブジェクトを考えるとIN/OUTでは言い表せない副作用が色々考えられる気がしますので色々なケースでIN/OUTがあっても結局ドキュメントを参照するはめになる気がしてなりません。(上に述べた1,2についてもIN/OUTよりは関数仕様に頼った方がいいのかも知れません。)

なおIN/OUTの表記の問題からちょっとはずれますが、複数のポインターや参照を引数に要求する複雑なインターフェースを改めて、struct/classを用いてデータ構造をまとめたりカプセル化する設計を通じて引数の数を減らす、あるいはレシーバー(=this)であるクラスのメンバー関数名を適切につけることで副作用をわかりやすくするといったことの方が(レシーバーや引数がどのように扱われるかが把握しやすくなるので)可読性を上げる効果が高い気がします。

投稿2016/12/10 05:12

KSwordOfHaste

総合スコア18394

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

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

Chironian

2016/12/10 06:31

回答、ありがとうございます。 > 何がIN/OUTのよい基準なのか混沌としてくる気がします。 ここなんですよね。考えれば考えるほど混乱してしまいます。 raccyさん回答へのコメント中に少し整理できたのですか、関数I/Fについてはその呼び出し元との入出力基準でI/Oを決めればよいような思います。 ただ、iostreamについてはそのoperator<<, >>で内部変数(例えばファイル・ポインタ)を変更してますので厳密に言えば入出力です。でも、プログラマが「普通」に意識する関数I/Fレベルでは入力です。 この意味では、KSwordOfHasteさんの「1.」の基準は妥当な感じがしますね。 ただ、プログラマが明確に入出力と意識するケースも「1.」に入ってしまうので悩ましい点も残ります。 > 自分は「IN/OUTの表記は可読性を上げる目的には充分有用でない」と思います。 結局、これかも知れませんね。混沌としてしまい白黒明確にできないものは明記しないのもありと思います。
guest

0

意図がはっきり分かったわけではないので、見当違いなコメントになりそうですが…

ストリーム限定で言えば、一番分かりやすいのは「書かない」ことではないかと思います。引数にするから混乱するのであり、引数に書かなければ解決する気がします。

要するに関数をファンクタにして、入出力に>>、<<を使えば、使うときにマニュアルを見なくてよくなります、間違っていたらコンパイルエラーになるだけですから。

ちなみに私は、引数である時点で関数への入力であるのだから、関数への入力と敢えて言う必要は皆無、だから入力と言うときはいわゆる入出力の場面の入力だけにしています。

投稿2016/12/11 05:25

majiponi

総合スコア1720

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

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

Chironian

2016/12/11 07:15 編集

あっとと、ごめんなさい。 実はこの元ネタはoperator<<()のオーバーロード関数についてだったのですよ。 std::ostream operator<<(std::ostream iOStream, DataClass const& iData); 的な記述のiOStreamについて、出力なのに i は違和感があると言う話からでした。 私自身も確かにそうだなって感じたので、ここで他の方の意見を参考に自分の考えを整理したくて質問させて頂きました。本質的に「仮引数の入出力をどのように説明するか?」と思ったので、通常の関数を例に挙げた次第です。 > 一番分かりやすいのは「書かない」ことではないかと思います。 確かにそれも有りと思います。 私自身は、プリフィクスを付けることで名前のかぶりを避けていること、プリフィクスでI/Oを判別できると便利などの二重三重のメリットがあるので i を付けてます。 そして、このために別のプリフィクスを割り当てるのもどうかと思うので。
guest

0

私もChironianさんのように引数には入出力の方向が判るようにiやoのプリフィックスを付け、コメントにin/outを書きます。そして、それは関数の呼び出し側と呼び出され側との間のデータの受け渡し方向で決めています。オブジェクトの役割まで考えに入れたら混乱しますので。

ご質問のような場合は、私も下のコードのように書きます。出力先ストリームとして渡した変数に何かを入れて返してもらうわけではないので。

投稿2016/12/10 11:09

catsforepaw

総合スコア5938

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

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

Chironian

2016/12/10 13:09

回答ありがとうございます。お久しぶりです。 元々このプリフィクスのアイデアはcatsforepawさんから頂いたものでしたね。 なかなか具合が良いです。ありがとうございます。 > 出力先ストリームとして渡した変数に何かを入れて返してもらうわけではないので。 確かにそうですね。呼び出し元とのI/F上の「意味」に着眼して決めるのが良さそうですね。
catsforepaw

2016/12/10 13:48

> 元々このプリフィクスのアイデアはcatsforepawさんから頂いたものでしたね。 お役に立っているようで嬉しいです。 ちょっといろいろあって、このところこのサイトをじっくり見ることが減ってしまいましたが、ゆるーく続けていこうとは思っています。
guest

0

出力先オブジェクトを引数として渡すのは少しもおかしくないですし、ごく普通の書き方だと感じます。

ただ、手続きプログラミング的でないとも言えます。「これを使ってこれしてくれ」という形ですから、高階関数ではないのですが関数プログラミング的なところがあり、これが手続きプログラミングにロックインした人にとっては不自然なシグネチャに見えるかもしれません。

そういう人も対象に含めてリーダビリティを高めるには、こうですかね。

write(data).to(out_stream);

自分じゃ絶対こんなの書きませんが。

投稿2016/12/10 03:18

yuba

総合スコア5568

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

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

Chironian

2016/12/10 03:32

回答ありがとうございます。 でも意図がうまく伝わらなかったようです。すいません。 そのout_streamに付けるコメントで、out_streamを出力と説明するか、入力と説明するか?という質問です。この名前の付け方から、出力ですね? そして、out_streamがファイル・ハンドルだった場合、どうされますか? ファイル・ハンドル自体の渡し方は入力なので。 ファイル・ハンドルの場合でも出力先であることは変わりないので、出力と説明すべきと思いますが、どうも抵抗を感じてしまうのです。
yuba

2016/12/10 03:44

ごめんなさい、やっと題意を理解できました。 コンピュータにとっては出力先、関数にとっては入力であるオブジェクトのことを日本語でどう表現するか、という問題ですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問