19

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

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

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

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

2 に答える 2

24

関数が新しいデータを構築して返すと仮定すると、値で返す必要があり、関数自体に type の変数を返す 1 つの戻りポイントvector<int>、または最悪の場合でもすべてが同じ変数を返す複数の戻りポイントがあることを確認してください。

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

次に、戻り値から、呼び出し元がそれに対して行うことへの潜在的なコピーを排除したいと考えています。解決するのは呼び出し側の問題であり、呼び出し先の問題ではありません。これを行うには、基本的に 3 つの方法があります。

  • 関数の呼び出しを a の初期化子として使用しますvector<int>。この場合も、信頼できる C++ コンパイラはコピーを省略します。
  • vector移動セマンティクスがある C++11 を使用します。
  • 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では必要ありませんが、もちろんそれでも機能します。

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

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

[*] もちろん、"as if" ルールは引き続き適用されます。これは簡単にコピー可能な型 ( ) のベクトルであり、intへのポインターを取得していないため、これを実現するのに十分スマートな C++ 実装を想像できます。任意の要素 (私が推測する) の場合、代わりにスワップでき、結果は「あたかも」コピーされます。しかし、私はそれを当てにしません。

于 2012-09-26T15:00:57.717 に答える
10

関数本体の構造によっては、戻り値の最適化が発生する可能性が非常に高くなります。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 のコピー構成ではなく移動コピーになります。

于 2012-09-26T14:51:21.667 に答える