C++:返還による参照のコピーコンストラクタ
-
20-09-2019 - |
質問
参考文献C++では枕す。:)
基本的な考え方としてはこのようにしている返却オブジェクトからの機能です。思い返すことなくポインタがその思いを手動で delete
では、なしで呼び出しのコピーコンストラクタで可能な場合(ための効率化、自然 追加: がなかったことだろうかな書きコピーコンストラクタ).
で、こちらのオプションを行うこととしているものであること
- の関数の戻り値型でなければならないと規定されて、クラス自体
MyClass fun() { ... }
または参照のクラスMyClass& fun() { ... }
). - この機能のいずれかを構築する変数の行を返し(
return MyClass(a,b,c);
)または戻り、既存の変数(MyClass x(a,b,c); return x;
). - このコードを受け取る変数で変数の型:(
MyClass x = fun();
またはMyClass& x = fun();
) - のコードを受け取る変数は変数をひとつ新規作成し、インタフェースを
MyClass x = fun();
又は割り当て、既存の変数(MyClass x; x = fun();
)
と考える:
- できるように悪いでもらいたいとの想いからの戻り値の型
MyClass&
でも結果を変数に取り壊されている前で返します。 - のコピーコンストラクタのみへといたる、すべての者に帰ったら、既存の変数となります。復帰時の可変構築に戻ることはありませんが呼び出されます。
- 私が割り当てた結果が、既存の変数のデストラクタまたは常にキック前の値を返します。また、コピーコンストラクタが呼び出され、対象の変数は受信のメンバー値のオブジェクトから返されます。
これらの結果はその矛盾を感じは全くかなりの混乱をきたしている。なので、何が起こっている。いつ、どのように適切に構築して返しオブジェクトから機能しているのでしょうか。
解決
C ++でのコピーを理解するための最良の方法は、人工的な例や楽器、それを生産しようとすることはありませんが多いです - コンパイラは、多かれ少なかれ、それが適当と考えるよう、コピーコンストラクタの呼び出しを削除し、追加の両方に許可されています。
ボトムライン - 。あなたが値を返す必要がある場合は、値を返すと、任意の「費用」の心配はありませんが、
他のヒント
推奨読書:効果的なC ++ スコット・マイヤーズ。あなたはそこに、このトピック(および多くの)については非常に良い説明を見つけます。
あなたが値で返す場合(コンパイラはそれらを離れて最適化していない限り - それは、あなたのいくつかの例では何が起こるかだ)簡単に言えば、コピーコンストラクタとデストラクタは、デフォルトで関与することになります。
。 地元であるあなたが参照(またはポインタ)で返す場合は、変数(スタック上に構築された)オブジェクトが戻り時に破壊されるので、あなたが結果としてダングリング参照を持っているので、あなたは、トラブルを招きます。
関数でオブジェクトを構築し、それを返す正規方法が値によるものである、ように:
MyClass fun() {
return MyClass(a, b, c);
}
MyClass x = fun();
これを使用する場合は、所有権の問題、ダングリング参照などを心配する必要はありません。そして、あなたが心配する必要はありませんので、コンパイラはほとんどの場合、あなたのための余分なコピーコンストラクタ/デストラクタの呼び出しを最適化しますどちらかのパフォーマンスます。
このオブジェクトは、関数から戻る際に破壊されないであろう -参照することにより(すなわち、ヒープ上の)new
によって構築されたオブジェクトを返すことが可能です。ただし、delete
を呼び出すことによって、どこか、後で明示的にそれを破壊する必要があります。
基準の値によって返されたオブジェクトを格納するためにも技術的に可能である、等
MyClass& x = fun();
しかし、私の知る限り、これを行うに多くのポイントがありません。一つは容易現在のスコープの外にあるプログラムの他の部分にこの参照を渡すことができ、特にため。しかし、x
によって参照されるオブジェクトは、できるだけ早くあなたが現在のスコープを残して破壊されるローカルオブジェクトです。したがって、このスタイルは厄介なバグにつながることができます。
RVO と言葉でNRVO(戻り値の最適化のためのこれら二つのスタンドと名前について読んRVO、そしてあなたが達成しようとしているものを行うために、コンパイラによって使用される最適化技術である)
あなたはstackoverflowの上、ここでの被験者の多くを見つけることができます。
作成した場合は、オブジェクトのようになります:
MyClass foo(a, b, c);
そのスタックの機能のフレーム。この機能が終了、そのフレームをポップのスタックとすべてのオブジェクトは、そのフレームが破壊. る方法はありません使えますよね。
うにしたい場合は返却オブジェクトを呼び出し側のみで、オプションがあります。
- 戻り値のコピーコンストラクタを要求するもののコピーコンストラクタが最適化を行います。
- 返すポインタを確認してくださいうちに使われているスマートポインタを取り扱うものとし、又は丁寧に削除できます。
を構築し、地元のオブジェクトとその参照を返しますこの地域の記憶を呼び出し側コンテキストではないコヒーレント-呼び出し範囲にアクセスできませんメモリをこの地方の呼びます。地域のメモリには有効期限の機能を所有しているので、別の言い方を、実行残さい。を理解しなければならないこのプログラム可能です。
それが参照を返すことは理にかなって唯一の時間についてです。明らか例えば、ほぼすべての入出力ストリームのメンバ関数は、入出力ストリームへの参照を返します。 iostream自体はメンバ関数のいずれかが呼び出される前に存在し、それらが呼び出されているの後に存在し続けます。
標準を使用すると、オブジェクトを返す際にコピーコンストラクタを呼び出す必要はありませんを意味し、「コピーの省略」を可能にします。これは、2つの形式があります:。名前戻り値の最適化(NRVO)と匿名の戻り値の最適化(通常はRVO)
それはの、おそらくのやや古いコンパイラの意味 -あなたが言っているから、あなたのコンパイラは、RVOはなくNRVOを実装しています。現在のほとんどのコンパイラは、両方を実装しています。この場合、非一致デストラクタは、それはおそらくGCC 3.4またはその近辺のようなものだ意味 - 私は確かにバージョンを覚えていないものの、このようなバグがあったの周り、その後1がありました。もちろん、それはあなたの計測がかなり右でないことも可能ですので、あなたが楽器なかったctorのは、使用されている、とのマッチングデストラクタは、そのオブジェクトに対して呼び出されます。
最後に、あなたはかかわらず、1つの単純な事実で立ち往生している:あなたがオブジェクトを返す必要がある場合は、オブジェクトを返す必要があります。具体的には、参照のみ既存のオブジェクト(の可能性の修正版)へのアクセスを与えることができる - それの目的は、同様にいくつかの点で構築しなければなりませんでした。あなたが問題を引き起こすことなく、いくつかの既存のオブジェクトを変更することができれば、それは大丈夫だとも、先に行くとそれを行います。あなたが既に持っているものとは別の独立した新しいオブジェクトが必要な場合は、先に行くとそれを行う - オブジェクトを事前に作成し、それへの参照を渡し、戻り速くそのものを作るかもしれないが、全体的に任意の時間を節約しません。オブジェクトを作成すると、関数の内部または外部で実行するかどうか、同じ費用について持っています。コンパイラはちょうど返されることになるだろうオブジェクトのためのスペースを割り当てる自動化、および機能を持っています - あなたはそれを返す関数でそれを作成するための余分なコストを支払うことはありませんので、任意の合理的に近代的なコンパイラは、RVOが含まれますそれがまだ機能が戻った後にアクセスできるでしょう、「場所に」構築ます。
基本的に、参照を返すことのみ意味をなします。あなたが破壊されているものへの参照を返す場合、コンパイラは警告を表示します。
の値によって参照ではなくオブジェクトを返すことは重要かもしれないオブジェクトをコピー保存します。
彼らは異なるsymanticsを持っているので、参照はポインタよりも安全ですが、舞台裏で、彼らはポインタです。
あなたのユースケースに応じて、1つの可能な解決法は、デフォルト・構築するためにオブジェクトを関数の外にある、の中にを取り、それを参照するなどのように、関数内で参照されるオブジェクトを初期化します:
void initFoo(Foo& foo)
{
foo.setN(3);
foo.setBar("bar");
// ... etc ...
}
int main()
{
Foo foo;
initFoo(foo);
return 0;
}
さて、もちろん、これはそれができません(または意味がありません)Foo
オブジェクトをデフォルト・構築し、後でそれを初期化する場合は動作しません。その場合は、そのコピー-建設を避けるために、あなたの唯一の現実的な選択肢は、ヒープ割り当てられたオブジェクトへのポインタを返すことです。
しかし、その後、あなたが最初の場所にコピー建設を避けるためにしようとしている理由を考えます。本当にあなたのプログラムに影響を与えコピー建設の「費用」である、またはこれは時期尚早の最適化の場合とは?
あなたはどちらかとstuckedあります:
1)ポインタを返す
のMyClassの*のFUNC(){ //いくつかのSTUF 新しいMyClassの(A、B、C)を返します。 }
2)オブジェクトのコピーを返します MyClassののFUNC(){ MyClassの(A、B、C)を返します。 }
目的関数は、クラスのメンバーであり、参照クラスのメンバである変数からのものである場合を除いて、FUNC範囲を出た後に破壊されるので、の参照を返すことは有効ではありません。
未直接の答えが、実行可能な提案:あなたはまた、auto_ptrをまたはsmart_ptrに包まれたポインタを、返すことができます。その後、コンストラクタとデストラクタが呼び出され、いつ何を得るの制御になります。