ある値 class T v を計算してそれをインスタンス class T a に代入したいとき
T f(...) //"..."は適当な引数
を定義して
a = f(...); //operaor =はそれなりに定義されているとします。
とやる方法と f(T&, ...) を定義して、
関数の中で代入をする方法があると思いますが
どちらがいいのでしょうか?関数の定義として
T& f(...)
というようなこともあり得るのでしょうか?
(そのときのreturnで返された
インスタンスの寿命はどうなっているのでしょうか?)
どうもこのあたりがよくわからず
無駄なことをやっているような気がしています。
どなたか、よろしくお願いします。
No.1
- 回答日時:
このケースでは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++の分からないところなのかなぁと思っています。
どうもありがとうございました。
No.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%というのは相当大きく見積もった例です)
ですから、インスタンス返しの方が見た目分かり易いですし、
実は時間もそんなに変わらないんだとしたら、
無理やりにリファレンス返しをする理由もないかも知れません
ので、その辺の理性的な検討もされては如何でしょう。
いろいろなパターンを示していただき
回答とアドバイスどうもありがとうございます。
とくに寿命でトラブルの起きやすい部分を
きっちり説明していただき、ちょっとすっきりしました。
私なりには
T& T::f2(...){ ~~~; return(*this); }
のような関数を用意して
T::f2(...)の中で自身を書きかえるようにして
他のインスタンスに代入する場合は
T a;
T v = a;
v.f2(...);
とやるのがいいのかな、と思うようになりました。
本当にどうもありがとうございました。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- その他(プログラミング・Web制作) どういうプログラムで組みますか?google colabでやってるんですけど、出来る方お願いします。 1 2022/07/17 18:41
- その他(プログラミング・Web制作) どういうプログラムで組みますか?google colabでやってるんですけど、出来る方お願いします。 1 2022/07/06 09:28
- その他(プログラミング・Web制作) このプログラミングをどう組みますか? Googlecolabでやってるんですが、出来る方お願いします 1 2022/07/13 10:52
- Java JavaのSingletonパターンのprivateの持つ意味が分かりません。 5 2022/06/12 10:38
- Visual Basic(VBA) Excel のユーザー定義関数でソルバーが動作しない 1 2022/09/05 19:51
- C言語・C++・C# 絶対ち 5 2022/10/09 17:36
- 政治 中国は一票の格差4倍で、日本は3倍ですが、それでも日本は民主主義国なら中国も同じですよね? 2 2023/03/16 04:52
- 小学校 (小学算数)「わり算の定義と文章題」について 2 2022/07/11 06:22
- 法学 法学の問題についてさっぱり分からないので○✕で教えてください 2 2022/10/23 01:05
- C言語・C++・C# C言語:数値の桁数指定についての質問です。 8 2022/05/26 23:53
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
CreateObjectとはどういう意味...
-
C# panel内のコントロールの使...
-
オブジェクト型の変数が定義さ...
-
Visual studio c# android オブ...
-
UMLでの「オブジェクト」と「イ...
-
ボタンを押すとラベルの文字を...
-
ゆかりネットで東北ずん子exを...
-
FriendとPublicの違い。。。
-
既定のコンストラクタがない?
-
VB.NETで、DLLを頂いたんですが...
-
名前空間について
-
クラスの作成方法を教えてください
-
(UWSC) 「#32770」の意味わかり...
-
MFCアプリのコマンドラインでパ...
-
DLLからEXEのクラスを呼び出す...
-
DataGridViewのセルに斜線を引...
-
excel vba グラフ データラベル...
-
VBのシステムの設計書にUMLは適...
-
ダイアログクラスのコントロー...
-
オーバーライド関数の呼び出し...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
CreateObjectとはどういう意味...
-
オブジェクト型の変数が定義さ...
-
コンストラクタ内でのthisポインタ
-
メモリリークが発生するのはど...
-
ボタンを押すとラベルの文字を...
-
クラス内にWin32APIのコールバ...
-
関数で値渡しと参照渡しではど...
-
VB6では、Applicationは未定義...
-
SetとNothingの存在意味?
-
オブジェクトをどこでdisposeす...
-
C# panel内のコントロールの使...
-
メモリ解放について、ご教授く...
-
イベントドリブンとオブジェク...
-
Visual studio c# android オブ...
-
最小化したフォームを元に戻す
-
すべてのページにServer.Create...
-
クラスの設定について
-
ADODB.Connectionはインターフ...
-
【VC++6.0(MFC)】「Out of memo...
-
デザイナ時のエラー「オブジェ...
おすすめ情報