web-dev-qa-db-ja.com

関数からSTLベクトルを返す-コピーコスト

関数からstlベクトルを返す場合:

vector<int> getLargeArray() {  ...  }

返品は費用のかかるコピー操作になりますか?ベクトル割り当てが高速であることをどこかで読んだことを覚えています-代わりに呼び出し元に参照を渡すように要求する必要がありますか?

void getLargeArray( vector<int>& vec ) {  ...  }
17
bobobobo

関数が新しいデータを作成して返すと仮定して、値で返す必要があります。関数自体に、タイプ_vector<int>_の変数を返す1つの戻り点、または最悪の場合、すべてが同じ変数。

これにより、信頼できるコンパイラで名前付きの戻り値の最適化が確実に得られ、潜在的なコピーのoneが削除されます(関数、戻り値へ)。戻り値を最適化する方法は他にもありますが、完全に予測できるわけではないため、単純なルールが安全に機能します。

次に、戻り値から呼び出し元がそれを使って行う可能性のあるコピーを排除します。解決するのは発信者の問題であり、着信者の問題ではありません。これを行うには、基本的に3つの方法があります。

  • 関数の呼び出しを_vector<int>_の初期化子として使用します。この場合も、信頼できるC++コンパイラーはコピーを削除します。
  • C++ 11を使用します。ここで、vectorには移動セマンティクスがあります。
  • C++ 03では、「swaptimization」を使用します。

つまり、C++ 03ではと書かないでください

_vector<int> v;
// use v for some stuff
// ...
// now I want fresh data in v:
v = getLargeArray();
_

代わりに:

_getLargeArray().swap(v);
_

これにより、v = getLargeArray()に必要な(省略してはならない[*])コピー割り当てが回避されます。高価なコピー代入の代わりに安価なムーブ代入があるC++ 11では必要ありませんが、もちろんそれでも機能します。

考慮すべきもう1つのことは、インターフェースの一部として実際にvectorが必要かどうかです。代わりに、出力イテレーターを受け取り、その出力イテレーターにデータを書き込む関数テンプレートを作成することもできます。ベクトルにデータが必要な呼び出し元は、_std::back_inserter_の結果を渡すことができ、dequeまたはlistにデータが必要な呼び出し元も渡すことができます。データのサイズを事前に知っている呼び出し元は、_back_insert_iterator_のオーバーヘッドを回避するために、ベクトルイテレータ(適切には最初にresize() d)または十分に大きい配列への生のポインタを渡すこともできます。同じことを行うテンプレート以外の方法もありますが、いずれかの方法でオーバーヘッドが発生する可能性があります。要素ごとにintをコピーするコストが心配な場合は、要素ごとの関数呼び出しのコストが心配です。

関数が新しいデータを作成して返すのではなく、既存の_vector<int>_の現在の内容を返し、元のデータを変更することが許可されていない場合、戻るときに少なくとも1つのコピーを回避することはできません。値で。したがって、そのパフォーマンスが証明された問題である場合は、値による戻り以外のAPIを調べる必要があります。たとえば、内部データをトラバースするために使用できるイテレータのペア、インデックスによってベクトル内の値を検索する関数、または(パフォーマンスの問題が内部を公開する必要があるほど深刻な場合)、ベクトルへの参照。明らかに、これらすべての場合で、関数の意味を変更します。呼び出し元に「自分のデータ」を与える代わりに、変更される可能性のある他の誰かのデータのビューを提供します。

[*]もちろん、「あたかも」ルールが引き続き適用されます。これは簡単にコピー可能な型(int)のベクトルであり、あなたが(私が推測する)どの要素へのポインタも受け取らなかった場合、代わりにスワップすることができ、結果は「あたかも」コピーしたかのようになります。しかし、私はそれを当てにしません。

23
Steve Jessop

関数本体の構造によっては、 戻り値の最適化 が得られる可能性が非常に高くなります。 C++ 11では、移動セマンティクスの恩恵を受けることもできます。値で返すことは確かによりクリーンなセマンティクスを持っており、プロファイリングでコストがかかることが証明されない限り、私はそれを好ましいオプションと見なします。良い関連記事があります ここ

これは、GCC(4.3.4)の古いバージョンを使用して、最適化またはC++ 11サポートなしでコンパイルされた詳細なダミークラスの小さな例です。

#include <vector>
#include <iostream>
struct Foo
{
  Foo() { std::cout << "Foo()\n"; }
  Foo(const Foo&) { std::cout << "Foo copy\n"; }
  Foo& operator=(const Foo&) { 
    std::cout << "Foo assignment\n"; 
    return *this;
  }
};

std::vector<Foo> makeFoos()
{
  std::vector<Foo> tmp;
  tmp.Push_back(Foo());
  std::cout << "returning\n";
  return tmp;
}

int main()
{
  std::vector<Foo> foos = makeFoos();
}

私のプラットフォームでの結果は、関数が戻る前にすべてのコピーが行われることです。 C++ 11サポートを使用してコンパイルすると、Push_backは、C++ 03コピー構造ではなく移動コピーになります。

10
juanchopanza