web-dev-qa-db-ja.com

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

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

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番目のソリューションをいつ、なぜ使用する必要がありますか?
152
phunehehe

コピーされた値は非効率的であるため、返されたくない

証明する。

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

きれいなコードを書くだけで、値で戻ります。パフォーマンスに問題がある場合は、プロファイルを作成(推測を停止)し、修正のためにできることを見つけます。関数から返されることはおそらくないでしょう。


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

ダイナミックアロケーションを使用する場合、実行できる最小限の方法はスマートポインタに配置することです。 (とにかく常にこれを行う必要があります)それから、何も削除する心配はありません、物事は例外セーフなどです。

103
GManNickG

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

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

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

41
Amir Rachum

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

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

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

Thing(const Thing& aThing) {}

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

更新

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

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 
}
11
demetrios

コピーコンストラクターが呼び出されているかどうかを判断する簡単な方法の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を取得せず、コピーコンストラクターのコストが非常に高い場合、then関数からインスタンスを返す別の方法を検索します。

8
dreamlax

まず、コードにエラーがあります。つまり、Thing *thing(new Thing());があり、return thing;だけがあります。

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

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

0
EMP