- ベストアンサー
代入の方法(C++)
ある値 class T v を計算してそれをインスタンス class T a に代入したいとき T f(...) //"..."は適当な引数 を定義して a = f(...); //operaor =はそれなりに定義されているとします。 とやる方法と f(T&, ...) を定義して、 関数の中で代入をする方法があると思いますが どちらがいいのでしょうか?関数の定義として T& f(...) というようなこともあり得るのでしょうか? (そのときのreturnで返された インスタンスの寿命はどうなっているのでしょうか?) どうもこのあたりがよくわからず 無駄なことをやっているような気がしています。 どなたか、よろしくお願いします。
- みんなの回答 (2)
- 専門家の回答
質問者が選んだベストアンサー
関数の中で定義されている変数(インスタンス)の 寿命を把握していれば、状況に合わせて適切なコード が書けるんじゃないでしょうか。 T& f(...)に対して、T a = f(...); または、 T& T::f(...)に対して、T a = v.f(...)も 当然ありです。 ただ、その際に問題となるのは返り値の寿命です。 返り値のインスタンスが、f(...)の中のローカル変数であっては いけない訳です。理由はお分かりと思いますが、ローカル変数は その変数の実行が終了した時点で解体されますので、参照先の アドレス上のインスタンスを指すリファレンスは、 解体後には使えないからです。ですから、 T& f1(...){ T ret; ~~~: return(ret); } ~~~ T a = f1(...); は、不可(動作不定)です。 では、どのような場合にリファレンス返しがOKなのか、 ということですが、最も代表的な例が、 T& T::f2(...){ ~~~; return(*this); } ~~~ T v; ~~~ T a = v.f2(...); です。自分自身へのリファレンスを返すメンバを定義して、 それを代入するわけです。こうすれば、リファレンスの指す オブジェクト(v)の寿命は、T v;が実行された関数内(グローバル に定義されたなら常に存在)であるので、同一関数内なら、 T a = v.f2(...);がOKになります。 その他の方法としては、あまりエレガントでありませんが、 T& f3(...){ static T ret; ~~~; return(T); } T& T::f4(...){ static T ret; ~~~; return(T); } という方法もあるでしょう。ただし、これは、マルチスレッド化した ときに問題が起こります。(retに関して相互排除しなければならない) あと、やってはいけないのは、 T& f5(...){ T *retp = new T; ~~~; return(*retp); } または、 T& T::f6(...){ T *retp = new T; ~~~; return(*retp); } です。これは、staticと同様、関数内のローカル変数の寿命の 問題をクリアしていますが、メモリリークが発生し易いコードです。 特に、 T a = f5(...); T a = v.f6(...); という文脈で使用すると、ほぼ100%(余程手の込んだコードを書かない 限り)、メモリリークが発生します。 なぜなら、f5やf6の中で割り当てたメモリを参照するポインタまたは リファレンスが、T;;operator=(const T&)の終了以降に、存在しなく なるため、newと必ず対になるべきdeleteを実行できなくなるからです。 とにかく、関数内で定義されたローカル変数は、関数終了時に 解体されるので、それを指すリファレンスを返すことはできない。 リファレンス返しをしたければ、 ・*thisへのリファレンスを返すメンバ関数 ・返り値用の静的変数を関数内で定義し、 それへのリファレンスを返すメンバ関数 などによって行う、ということになると思います。 また、ここからは回答でなくアドバイスですが、 上記の例のクラスTが余程大規模であるか、 または、代入演算が余程頻繁に行われるかしない限り、 インスタンス返しによるオーバーヘッドは、 気にするほど大きなものにはならないと思います。 もし、あるクラスTに対して、インスタンス返しの実行時間が、 リファレンス返しの1.5倍かかったとしても、その代入計算が 行われる時間が、プログラムの全実行時間の1%しかないので あれば、0.5%の違いしか生じないわけです。(全実行時間の 1%というのは相当大きく見積もった例です) ですから、インスタンス返しの方が見た目分かり易いですし、 実は時間もそんなに変わらないんだとしたら、 無理やりにリファレンス返しをする理由もないかも知れません ので、その辺の理性的な検討もされては如何でしょう。
その他の回答 (1)
- bob
- ベストアンサー率50% (52/103)
このケースでは2通り考えられます。 (1) void T :: f( const T& v, ...) { ~~~; } もしくは T& T :: f( const T& v, ...) { ~~~; return *this; } を定義して a.f(v, ...); でaを書き換える (2) T& T :: f(...) { ~~~; return *this; } を定義して a = v.f(...); でaを書き換える どちらを選ぶかはオブジェクト指向ですから、T::fがどう言う意味を持つかによって選ぶのがいいと思います。(1)だと他のオブジェクトvとその他の引き数を入力にしてオブジェクト自身を書き換えるメソッドになりますし、(2)だと本質はその他の引き数でオブジェクト自身を書き換えることで、その結果をたまたま他のオブジェクトaにコピーしているという解釈になります。 a=v.f(...); 以外の形でメソッドfを使うことが無いようなら(1)の方が明示的でいいかも知れません。vを書き換えたくない場合も(1)を使うことになります。 返り値に&をつけると、返り値のオブジェクトが左辺値に使えるようになります。寿命は参照するオブジェクトの寿命と同じで、それ以降は値の保証はないです。上の2例では*thisを返しているのでメソッドを呼び出したオブジェクトの寿命と等しくなります。 返り値に&がつかない場合、特に(2)では一時オブジェクトに返り値がコピーされたのをまたaにコピーすると言う形になると思われるので2度手間の印象があります。
お礼
どうもありがとうございます。 (2)の解釈なのですが、やりたいこととしては b = a.f(...); のような場合と a = a.f(...); の場合の2つがあり、 単なるf(...) とaにaを変更するメソッドf(a,...) を用意するようなことは 面倒であるとともに、メンテナンス上よくないような気がしています。 (実際問題としてはf(a,...) の中で a = a.f(...)を実行すればいいのでしょうけれど) この場合、新しいインスタンスvに対して初期化または代入により v=a; という状態を実現し、 a=v.(...); というのもちょっと変なような気もするんです。 どうもこのあたりのもやもやがC++の分からないところなのかなぁと思っています。 どうもありがとうございました。
お礼
いろいろなパターンを示していただき 回答とアドバイスどうもありがとうございます。 とくに寿命でトラブルの起きやすい部分を きっちり説明していただき、ちょっとすっきりしました。 私なりには T& T::f2(...){ ~~~; return(*this); } のような関数を用意して T::f2(...)の中で自身を書きかえるようにして 他のインスタンスに代入する場合は T a; T v = a; v.f2(...); とやるのがいいのかな、と思うようになりました。 本当にどうもありがとうございました。