C ++で「オブジェクトを返す」方法は?


167

同じような質問がたくさんあるので、タイトルはなじみがあるように聞こえますが、問題の別の側面を求めています(スタックに物を置くこととヒープに置くことの違いを知っています)。

Javaでは、常に「ローカル」オブジェクトへの参照を返すことができます

public Thing calculateThing() {
    Thing thing = new Thing();
    // do calculations and modify thing
    return thing;
}

C ++では、似たようなことをするために2つのオプションがあります

(1)オブジェクトを「返す」必要があるときはいつでも参照を使用できます

void calculateThing(Thing& thing) {
    // do calculations and modify thing
}

次に、このように使用します

Thing thing;
calculateThing(thing);

(2)または、動的に割り当てられたオブジェクトへのポインタを返すことができます

Thing* calculateThing() {
    Thing* thing(new Thing());
    // do calculations and modify thing
    return thing;
}

次に、このように使用します

Thing* thing = calculateThing();
delete thing;

最初の方法を使用すると、メモリを手動で解放する必要はありませんが、コードを読みにくくします。2番目のアプローチの問題は、覚えておかなければならないことです。delete thing;です。コピーした値を返すのは非効率的だと思います(私は思う)ので、ここで質問をします

  • 3番目の解決策はありますか(値をコピーする必要はありません)?
  • 最初の解決策に固執しても問題はありませんか?
  • いつ、なぜ第2のソリューションを使用する必要がありますか?

32
質問を上手に入れるための+1。
2010

1
「関数は何かを返す」と言うのは非常に面倒ですが、少し不正確です。より正確には、関数呼び出しを評価すると値が生成されます。値は常にオブジェクトです(それがvoid関数でない限り)。違いは、値がglvalueであるかprvalueであるかです。宣言された戻り値の型が参照であるかどうかによって決定されます。
Kerrek SB 2014

回答:


107

効率が悪いので、コピーした値を返したくない

証明する。

RVOとNRVO、およびC ++ 0xの移動セマンティクスを調べます。ほとんどの場合、C ++ 03では、出力パラメーターはコードを醜くするための良い方法であり、C ++ 0xでは実際には出力パラメーターを使用することで自分自身を傷つけます。

きれいなコードを書いて、値で返すだけです。パフォーマンスに問題がある場合は、それをプロファイリング(推測を停止)し、修正するために何ができるかを見つけます。関数からは何も返さないでしょう。


そうは言っても、そのように書くことに夢中になっているなら、おそらくoutパラメータを実行したいと思うでしょう。安全で一般的に高速な動的メモリ割り当てを回避します。関数を呼び出す前にオブジェクトを構築する何らかの方法が必要ですが、すべてのオブジェクトに対して常に意味があるとは限りません。

動的割り当てを使用する場合は、スマートポインタに配置するだけで済みます。(とにかくこれは常に行う必要があります)次に、何も削除したり、例外が安全であったりするなどの心配はありません。唯一の問題は、とにかく値で返すよりも遅いことです!


10
@phunehehe:推測するポイントはありません。コードのプロファイルを作成して調べる必要があります。(ヒント:いいえ。)コンパイラーは非常に賢いので、コピーする必要がない場合でも、コピーに時間を費やすことはありません。コピーにコストがかかる場合でも、高速なコードよりも優れたコードを追求する必要があります。速度が問題になる場合、優れたコードは最適化が容易です。あなたが考えていない何かのためにコードを醜くすることは問題ではありません。特にあなたが実際にそれを遅くするか、それから何も得ない場合。また、C ++ 0xを使用している場合、移動セマンティクスにより、これは問題になりません。
GManNickG 2010

1
@GMan、re:RVO:実際にこれが当てはまるのは、呼び出し元と呼び出し先がたまたま同じコンパイルユニットにいる場合だけです。実際には、ほとんどの場合そうではありません。したがって、コードがすべてテンプレート化されていない場合(この場合はすべて1つのコンパイル単位に含まれます)、またはリンク時の最適化が行われている場合(GCCは4.5からのみ)、失望することになります。
Alex B

2
@Alex:コンパイラは翻訳単位全体の最適化にますます良くなっています。(VCは現在、いくつかのリリースでそれを行っています。)
sbi 2010

9
@アレックスB:これは完全なゴミです。多くの非常に一般的な呼び出し規約では、呼び出し元に大きな戻り値用のスペースを割り当てる責任があり、呼び出し先にはその構築が必要です。RVOは、リンク時の最適化がなくても、コンパイルユニット全体で問題なく動作します。
CBベイリー

6
@Charles、確認すると、正しいようです!私は明らかに誤った情報を含んだ声明を撤回します。
Alex B

41

オブジェクトを作成して返すだけです

Thing calculateThing() {
    Thing thing;
    // do calculations and modify thing
     return thing;
}

最適化を忘れて、読み取り可能なコードを書くだけなら、自分で有利になると思います(後でプロファイラーを実行する必要がありますが、事前に最適化しないでください)。


2
Thing thing();ローカル関数を宣言し、を返しますThing
dreamlax 2010

2
Thingthing()は、Thingを返す関数を宣言します。関数本体にはThingオブジェクトは作成されていません。
CBベイリー

@dreamlax @Charles @GMan少し遅れましたが、修正されました。
Amir Rachum

これはC ++ 98でどのように機能しますか?CINTインタープリターでエラーが発生し、C ++ 98またはCINT自体が原因であるのではないかと思っていました...!
xcorat

16

次のようなオブジェクトを返すだけです:

Thing calculateThing() 
{
   Thing thing();
   // do calculations and modify thing
   return thing;
}

これにより、Thingsのコピーコンストラクターが呼び出されるため、独自の実装を行うことができます。このような:

Thing(const Thing& aThing) {}

これは少し遅くなるかもしれませんが、まったく問題にならないかもしれません。

更新

コンパイラーはおそらくコピー・コンストラクターへの呼び出しを最適化するため、余分なオーバーヘッドはありません。(dreamlaxがコメントで指摘したように)。


9
Thing thing();を返すローカル関数を宣言しますThing。また、標準では、コンパイラーが提示した場合にコピーコンストラクターを省略できます。最新のコンパイラはおそらくそれを行うでしょう。
dreamlax 2010

1
特にディープコピーが必要な場合は、コピーコンストラクターを実装することで良い点が得られます。
mbadawi23 2014年

コピーコンストラクターについて明示的に述べるための+1。ただし、@ dreamlaxが言うように、コンパイラーはおそらく関数の戻りコードを「最適化」して、コピーコンストラクターへの実際に必要でない呼び出しを回避します。
jose.angel.jimenez 14

2018年、VS 2017では、移動コンストラクターを使用しようとしています。移動コンストラクターが削除され、コピーコンストラクターが削除されていない場合は、コンパイルされません。
アンドリュー

11

auto_ptrのようなスマートポインターを使用しようとしましたか(Thingが本当に大きくて重いオブジェクトの場合):


std::auto_ptr<Thing> calculateThing()
{
  std::auto_ptr<Thing> thing(new Thing);
  // .. some calculations
  return thing;
}


// ...
{
  std::auto_ptr<Thing> thing = calculateThing();
  // working with thing

  // auto_ptr frees thing 
}

4
auto_ptrsは非推奨です。shared_ptrまたはunique_ptr代わりに使用します。
MBraedley 2017

これをここに追加するだけです...私は長年c ++を使用してきましたが、c ++の専門家ではありませんが...スマートポインターを使用しないようにしようと決心しました。一種の問題であり、実際にはコードの高速化にも役立ちません。RAIIを使用して、自分でデータをコピーしてポインタを自分で管理したいだけです。したがって、可能であれば、スマートポインタを使用しないことをお勧めします。
アンドリュー

8

コピーコンストラクターが呼び出されているかどうかを確認する簡単な方法の1つは、クラスのコピーコンストラクターにログを追加することです。

MyClass::MyClass(const MyClass &other)
{
    std::cout << "Copy constructor was called" << std::endl;
}

MyClass someFunction()
{
    MyClass dummy;
    return dummy;
}

電話someFunction; 取得される「コピーコンストラクターが呼び出されました」行の数は0、1、2の間で変化します。何も取得されない場合、コンパイラーは戻り値を最適化しています(これは許可されています)。あなたは0を得ることはありません取得し、あなたのコピーコンストラクタは、途方もなく高価である場合は、その後、あなたの関数からインスタンスを返すために別の方法を検索します。


1

まず、あなたが持っている意味、コードのエラーを持っているThing *thing(new Thing());、とだけreturn thing;

  • を使用しshared_ptr<Thing>ます。ポインタだったので、それを参照してください。最後に参照したときに削除されますThing含まが範囲外。
  • 最初のソリューションは、単純なライブラリでは非常に一般的です。ある程度のパフォーマンスと構文上のオーバーヘッドがありますが、可能であれば避けてください
  • 2番目のソリューションは、例外がスローされないことが保証できる場合、またはパフォーマンスが非常に重要な場合にのみ使用してください(これが適切になる前に、Cまたはアセンブリとのインターフェイスを確立します)。

0

C ++のエキスパートがより良い答えをもたらすと確信していますが、個人的には2番目のアプローチが好きです。スマートポインタを使用すると、忘れるという問題に役立ちdeleteます。言うまでもなく、事前にオブジェクトを作成する必要がある(そして、ヒープに割り当てる場合は削除する必要がある)よりも見た目がきれいです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.