C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[6]: メンバ関数を静的にするかどうか


(過去ログ 67 を表示中)

[トピック内 10 記事 (1 - 10 表示)]  << 0 >>

■38835 / inTopicNo.1)  メンバ関数を静的にするかどうか
  
□投稿者/ 七篠 (45回)-(2009/07/26(Sun) 01:11:36)

分類:[C/C++] 

こんばんは。
現在、関数の定義の仕方に悩んでいます。

決めているのは余計なインスタンスが作られないように、インスタンスを返す関数は作らずに
結果を格納する参照変数で対応するということだけです。

例えば数学のベクトルクラスの関数で、外積を求める関数の実装方法として
 1:ひとつの引数を取り、自身と引数の外積の結果を自身に格納する。
 2:引数を二つとり、それらの計算結果を自身に格納する。
 3:静的関数として宣言。引数を計算対照2つ、結果格納用1つの三つとる。
のような方法が考えられますが、どれが一番いいのでしょうか?

インスタンスの値を変更しないものは静的関数にした方がいいということを何処かで聞いた気がするのですが、
結果が同じベクトルなので、そのまま自身に収めてしまってもいい気もします。

私の持っている参考書によると、「データ表現にアクセスしなければならないが、特定のオブジェクトを対象として
呼び出す必要がない場合は static 宣言せよ」とあるのですが、これはあるベクトルの長さを取得する関数は
非静的で、ある二つのベクトルの外積を求める関数は静的にせよ。ということでしょうか?

アドバイスや、参考になる書籍・webページなどをご存知の方がいらっしゃいましたら、
教えていただけると幸いです。
引用返信 編集キー/
■38839 / inTopicNo.2)  Re[1]: メンバ関数を静的にするかどうか
□投稿者/ 774RR (370回)-(2009/07/26(Sun) 07:00:33)
> 余計なインスタンスが作られないように、インスタンスを返す関数は作らずに
というあたりがすでに方針として間違っている可能性がある。

普通に int や double の演算を行う記述と同じようにベクトル演算も記述できるか?
というあたりを考えるとなんとなく方針が見えてくるかもしれない・・・
普通に x+y という式を考えると、これは x でも y でもない、新しい値が返されている。
同様に x+=y であれば、これは x が新しい値で上書きされている。
operator のオーバーロードも static と non-static の両方を持っているよな。

というわけで、外積も同じなんぢゃないか?
A×B と書いて A でも B でもない新しいベクトルを得る
A×=B と書いて A に新しい値を上書きする
両方の演算子があってもおかしくない。

ライブラリを作る側の理屈だけを追いかけるのではなくて、ライブラリを使う側の都合を考えよう。
外積を求める処理をどう書ければ、使う側にとって便利なのか?で決めたらいい。
その結果として求める方法が複数個あってもいいんぢゃないかな。
引用返信 編集キー/
■38854 / inTopicNo.3)  Re[1]: メンバ関数を静的にするかどうか
□投稿者/ Jitta on the way (359回)-(2009/07/26(Sun) 19:06:44)
No38835 (七篠 さん) に返信
> 決めているのは余計なインスタンスが作られないように、インスタンスを返す関数は作らずに
> 結果を格納する参照変数で対応するということだけです。

そう“決まっている”なら、そう作るしかないですよね?
もし、課題として与えられているなら、疑問をはさむ余地はありません。そういう作り方ができるかどうかを見るための課題ですから。
プロジェクトで、そう作るように決められている場合も、例外的なことをしてメンテナンス コストを増加させるかもしれないので、従う方がいいでしょう。
下でもう一度書きますが、“なぜ?”そう決まっているのか、明らかにしましょう。

でも、「結果を格納する参照変数で対応する」っていうのは、なんのことだかわからない。


> インスタンスの値を変更しないものは静的関数にした方がいいということを何処かで聞いた気がするのですが
インスタンスの値を変更しなくても、インスタンスの値を参考にする場合は、static に出来ないですよね?


> 私の持っている参考書によると、「データ表現にアクセスしなければならないが、特定のオブジェクトを対象として
> 呼び出す必要がない場合は static 宣言せよ」とあるのですが
“なぜ”、そうしろ(本当に「せよ」です?「した方がよい」ではなく?)と、その参考書は言っていますか?

引用返信 編集キー/
■38863 / inTopicNo.4)  Re[2]: メンバ関数を静的にするかどうか
□投稿者/ 倉田 有大 (687回)-(2009/07/26(Sun) 22:34:36)
もうかかれてますが、メソッドより演算子のオーバーロードという手があるかも。
昔マトリックスなら、演算子のオーバーロードで計算させたかなあ。
引用返信 編集キー/
■38932 / inTopicNo.5)  Re[3]: メンバ関数を静的にするかどうか
□投稿者/ 七篠 (46回)-(2009/07/28(Tue) 01:36:14)
お返事ありがとうございます。


> 決めているのは余計なインスタンスが作られないように、インスタンスを返す関数は作らずに
> 結果を格納する参照変数で対応するということだけです。
これは「戻り値最適化」というキーワードを調べた際、一時オブジェクトを減らすことが
最適化になるという記述を見つけたためにそうしようと思いました。

方法をひとつに絞るというような記述は、計算方法は便利なものはいくつあってもいいと
思うのですが、上記の件と、書いているプログラムが完全に趣味で、自分以外が使わない為に
多少自由が利かなくても自分が分かっていればいいと思ったためです。つまり
Vector3 v = vec0 * vec1;
という書き方は一行に収まってすっきりするけど、自分しか使わないから
Vector3 v = vec0;
v *= v1;
というやり方を強制してもいいよね?と思ってしまったわけです。

> でも、「結果を格納する参照変数で対応する」っていうのは、なんのことだかわからない。
これは例えば

@ Vector3 Cross(Vector3& v0, Vector3& v1);
 v0 と v1 で外積を求め、答えを格納した一時変数を返す。
 
A Void Corss(Vector3& v0, Vector3& v1 Vector3& out);
 v0 と v1 で外積を求め、答えを out に格納する。

@の方法を使わずにAの方法を使うということです。

> “なぜ”、そうしろ(本当に「せよ」です?「した方がよい」ではなく?)と、その参考書は言っていますか?
残念ながら、その理由についての記述はありませんでした。

> インスタンスの値を変更しなくても、インスタンスの値を参考にする場合は、static に出来ないですよね?
これはどういうことでしょうか? クラス内 static 関数であれば、引数のオブジェクトの
private 変数にもアクセスできますが… 広域関数でしょうか?

演算子のオーバーロードについては一応実装してあります。
四則演算子の表現に合わないような関数のインターフェースに悩んでいます。
決める…とは言ったものの、とんでもない勘違いなコードを書いている気が
しまして…(前にもポインタ・参照変数を全部デファインしてたら「他の人
が見て混乱する」と言われて止めました)。

引用返信 編集キー/
■38940 / inTopicNo.6)  Re[4]: メンバ関数を静的にするかどうか
□投稿者/ Jitta on the way (363回)-(2009/07/28(Tue) 07:29:09)
No38932 (七篠 さん) に返信
>>でも、「結果を格納する参照変数で対応する」っていうのは、なんのことだかわからない。
> これは例えば
>
> @ Vector3 Cross(Vector3& v0, Vector3& v1);
>  v0 と v1 で外積を求め、答えを格納した一時変数を返す。
>  
> A Void Corss(Vector3& v0, Vector3& v1 Vector3& out);
>  v0 と v1 で外積を求め、答えを out に格納する。
>
> @の方法を使わずにAの方法を使うということです。
>

んー?
1でも2でも、結果を格納する変数を作らなければならない、ってのは同じですよ?
また、戻り値がひとつ(ひとつの意味の塊)であるのだから、1の方が自然な書き方ではないでしょうか。



>>インスタンスの値を変更しなくても、インスタンスの値を参考にする場合は、static に出来ないですよね?
> これはどういうことでしょうか? クラス内 static 関数であれば、引数のオブジェクトの
> private 変数にもアクセスできますが… 広域関数でしょうか?

そうですね。んー、と、Equal メソッドなんかは、2つの引数を取るものと、1つの引数をとるものがあります。これは、実装するインターフェースによって異なるのですが、これは、インターフェースがどういう意味を持っているか(あるいは、実装するものにものに持たせるか)によって決められます。
あ、これは、static じゃないか。string.compare が、静的動的ふたつの実装をしてなかったかな?
実体に対して働きかけるもの、働きかけられるものについては、動的なものが用意されている方が、使いやすいと思います口口
引用返信 編集キー/
■38941 / inTopicNo.7)  Re[4]: メンバ関数を静的にするかどうか
□投稿者/ 774RR (372回)-(2009/07/28(Tue) 07:32:02)
> Vector3 v = vec0;
> v *= v1;
> というやり方を強制してもいいよね?と思ってしまったわけです。
別にかまわないと思うよ。でもたぶん、そういう記述を数箇所行った時点で
「あー、めんどくさい、なぜこんな書き方しかできないようにしちゃったんだろ」と後悔すると思う。

ライブラリを作るのは1回で済む。
ライブラリを使う場所は1箇所どころではない。
と考えれば、使う側で便利なように記述するのが *俺は* 当然の配慮だと思う。

static メンバーを使ったり friend を使ったり non-static メンバーを使ったり、というのは
単に「実装を実現する手法」にすぎないわけだ。

***** のような記述をするには +++++ 機能を使えばもっとも適切だと自分は思った。
だから +++++ を使った。というのがまっとうなプログラミングの順番だと思う。

+++++ 機能を使えばいいと聞いたからなにがなんでも +++++ を使った。その結果使いにくいものになった。
では本末転倒。

> とんでもない勘違いなコードを書いている気がしまして…
これは他人のコードをどれだけ読んだか/他人に自分のコードをどれだけ読んでもらったかの経験の差だろう。
こういう場で批評をもらう(痛い目見るの覚悟のうえで)といいんぢゃないかな。
引用返信 編集キー/
■39236 / inTopicNo.8)  Re[5]: メンバ関数を静的にするかどうか
□投稿者/ 七篠 (47回)-(2009/08/01(Sat) 22:02:25)
お返事ありがとうございます。

> 「あー、めんどくさい、なぜこんな書き方しかできないようにしちゃったんだろ」と後悔すると思う。

そうですね… 確かに処理を少なくしようとして、めんどくさいことが結構思い当たります。
文も理解するのにまわりくどくなってしまい、メンテナンスにも支障が出そうです。

これからは余程実行に支障が出ないような場合は、便利さ・分かりやすさも考えて書こうと思います。

みなさん、ありがとうございました!
解決済み
引用返信 編集キー/
■39237 / inTopicNo.9)  Re[5]: メンバ関数を静的にするかどうか
□投稿者/ 七篠 (48回)-(2009/08/01(Sat) 22:08:59)
お返事ありがとうございます(順番が前後してごめんなさい)

No38940 (Jitta on the way さん) に返信
> んー?
> 1でも2でも、結果を格納する変数を作らなければならない、ってのは同じですよ?
> また、戻り値がひとつ(ひとつの意味の塊)であるのだから、1の方が自然な書き方ではないでしょうか。

そうなのですが、1の方法は戻り値用にオブジェクトの生成が一回多くはさまれてしまうので、
2の方法がいいのではないかと考えたのです。

しかし、文を見た際の分かりやすさ・それに伴うメンテナンス性の高さは1の方がいいですね。
引用返信 編集キー/
■39261 / inTopicNo.10)  Re[6]: メンバ関数を静的にするかどうか
□投稿者/ 774RR (375回)-(2009/08/04(Tue) 09:31:37)
> そうなのですが、1の方法は戻り値用にオブジェクトの生成が一回多くはさまれてしまうので、
ああ、なるほど RVO (Return Value Optimization) について誤解しているようだ。

T func() { ... } // 値を返す関数があるとき
T x=func(); // その関数の返却する値で変数 x を初期化しているつもり
というコードがあったとする(ごく普通に見るありがちなコード)

言語規格書が主張するところの文法解釈上、最適化がない純粋抽象機械は確かに
1. func() が返却値用の一時オブジェクトを作成する (tobj と呼ぶことにする)
2. x が tobj からコピーコンストラクトされる
3. tobj がデストラクトされる
という動きを行う。
※なので tobj の生成破棄が無駄になる、と七篠氏は考えているわけだ。
→だから tobj の生成破棄を「ソースレベルで」行わせないようにコードを書くべし!
=七篠氏の考えている RVO とは「プログラマがソースを書く上で工夫すること」である、と。

それは違う。 RVO は「コンパイラが勝手に行うこと」だ。
先の単純なコードを、条件が許せばコンパイラは以下のように翻訳してよい。
a. func() の呼び出しシーケンスまたは終了シーケンスの一部として x 用の記憶域を確保する
(記憶域を用意するだけで、まだコンストラクトしない)
b. func() の返却値を先に確保した x 用の記憶域上にコンストラクトする
コンパイラは、最適化処理の都合が許せば一時オブジェクトの生成破棄を省略してよい
( T のコンストラクタ・デストラクタに副作用があっても省略してよい)
というのが RVO なのであって、ソースレベルでプログラマが工夫することではない。

だから Vector3 v = vec0 * vec1; と書いても、コンパイラにその性能があれば
一時オブジェクトが生成されることなく operator * の結果から v が直接生成される。

参考記事
http://ml.tietew.jp/cppll/cppll/article/12750
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -