web-dev-qa-db-ja.com

C ++でstd :: vectorを返す効率的な方法

関数でstd :: vectorを返すときのデータの量と、std :: vectorを(ヒープ上の)free-storeに配置し、代わりにポインターを返す最適化の大きさ:

std::vector *f()
{
  std::vector *result = new std::vector();
  /*
    Insert elements into result
  */
  return result;
} 

より効率的:

std::vector f()
{
  std::vector result;
  /*
    Insert elements into result
  */
  return result;
} 

60
Morten

C++ 11では、これが推奨される方法です。

std::vector<X> f();

つまり、値で戻ります。

C++ 11では、std::vectorにはmove-semanticsがあります。つまり、関数で宣言されたlocalベクトルは、戻り時にmovedになり、場合によってはコンパイラによって移動さえ省略できます。

87
Nawaz

値で戻る必要があります。

この標準には、値による返却の効率を改善する特定の機能があります。 「コピー省略」、より具体的にはこの場合「名前付き戻り値最適化(NRVO)」と呼ばれます。

コンパイラーはそれを実装する必要はありませんが、関数のインライン化を実装する(または最適化を実行する)ために、コンパイラーは再びhaveを実装する必要はありません。しかし、コンパイラーが最適化しない場合、標準ライブラリーのパフォーマンスはかなり低下する可能性があり、すべての深刻なコンパイラーはインライン化とNRVO(およびその他の最適化)を実装します。

NRVOが適用されると、次のコードにはコピーがありません。

_std::vector<int> f() {
    std::vector<int> result;
    ... populate the vector ...
    return result;
}

std::vector<int> myvec = f();
_

しかし、ユーザーはこれをしたいかもしれません:

_std::vector<int> myvec;
... some time later ...
myvec = f();
_

コピー省略は、初期化ではなく割り当てであるため、ここでのコピーを妨げません。ただし、still value by returnにする必要があります。 C++ 11では、割り当ては「移動セマンティクス」と呼ばれる別の何かによって最適化されます。 C++ 03では、上記のコードはコピーを引き起こします。理論上オプティマイザーはそれを回避できるかもしれませんが、実際には非常に困難です。したがって、C++ 03ではmyvec = f()の代わりに次のように記述する必要があります。

_std::vector<int> myvec;
... some time later ...
f().swap(myvec);
_

別のオプションがあります。これは、ユーザーにより柔軟なインターフェイスを提供することです。

_template <typename OutputIterator> void f(OutputIterator it) {
    ... write elements to the iterator like this ...
    *it++ = 0;
    *it++ = 1;
}
_

さらに、その上で既存のベクトルベースのインターフェイスをサポートすることもできます。

_std::vector<int> f() {
    std::vector<int> result;
    f(std::back_inserter(result));
    return result;
}
_

このmightは、既存のコードがreserve()を前もって固定された量よりも複雑な方法で使用している場合、既存のコードよりも効率が低下します。しかし、既存のコードが基本的にベクターで_Push_back_を繰り返し呼び出す場合、このテンプレートベースのコードは同じように優れているはずです。

40
Steve Jessop

[〜#〜] rvo [〜#〜] についての答えを投稿するときです。

オブジェクトを値で返す場合、コンパイラは多くの場合これを最適化して、2回構築されないようにします。これは、関数で一時オブジェクトとして構築してからコピーするのは不要だからです。これは戻り値の最適化と呼ばれます。作成されたオブジェクトはコピーされずに移動されます。

3
user529758

C++ 11より前の一般的なイディオムは、入力されるオブジェクトへの参照を渡すことです。

その後、ベクターのコピーはありません。

void f( std::vector & result )
{
  /*
    Insert elements into result
  */
} 
1
Drew Dormann
vector<string> getseq(char * db_file)

また、main()で印刷する場合は、ループで実行する必要があります。

int main() {
     vector<string> str_vec = getseq(argv[1]);
     for(vector<string>::iterator it = str_vec.begin(); it != str_vec.end(); it++) {
         cout << *it << endl;
     }
}
0
Akash Kandpal

はい、値で戻ります。コンパイラはそれを自動的に処理できます。

0
bruce

コンパイラがNamed Return Value Optimizationhttp://msdn.Microsoft.com/en-us/library/ms364057(v = vs.80).aspx )をサポートしている場合、直接返すことができますベクトルは次のものがないことを提供します。

  1. 異なる名前のオブジェクトを返す異なるパス
  2. EH状態が導入された複数のリターンパス(同じ名前のオブジェクトがすべてのパスで返される場合でも)。
  3. 返される名前付きオブジェクトは、インラインasmブロックで参照されます。

NRVOは、冗長なコピーコンストラクターとデストラクターの呼び出しを最適化するため、全体的なパフォーマンスが向上します。

あなたの例には実際の差分はありません。

0
taocp

「値による戻り」と同じくらい素晴らしいことですが、エラーにつながる可能性のある種類のコードです。次のプログラムを検討してください。

    #include <string>
    #include <vector>
    #include <iostream>
    using namespace std;
    static std::vector<std::string> strings;
    std::vector<std::string> vecFunc(void) { return strings; };
    int main(int argc, char * argv[]){
      // set up the vector of strings to hold however
      // many strings the user provides on the command line
      for(int idx=1; (idx<argc); ++idx){
         strings.Push_back(argv[idx]);
      }

      // now, iterate the strings and print them using the vector function
      // as accessor
      for(std::vector<std::string>::interator idx=vecFunc().begin(); (idx!=vecFunc().end()); ++idx){
         cout << "Addr: " << idx->c_str() << std::endl;
         cout << "Val:  " << *idx << std::endl;
      }
    return 0;
    };
  • Q:上記を実行するとどうなりますか? A:コアダンプ。
  • Q:コンパイラーがなぜミスをキャッチしなかったのですか? A:プログラムは、意味論的ではありませんが、構文的に正しいためです。
  • Q:vecFunc()を変更して参照を返すとどうなりますか? A:プログラムは完了するまで実行され、期待される結果が生成されます。
  • Q:違いは何ですか? A:コンパイラは、匿名オブジェクトを作成および管理する必要はありません。プログラマーは、壊れた例のように2つの異なるオブジェクトではなく、イテレーターとエンドポイントの決定に1つのオブジェクトのみを使用するようにコンパイラーに指示しました。

上記のエラープログラムは、GNU g ++レポートオプション-Wall -Wextra -Weffc ++を使用してもエラーがないことを示します。

値を生成する必要がある場合、vecFunc()を2回呼び出す代わりに次のように機能します。

   std::vector<std::string> lclvec(vecFunc());
   for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)...

上記もループの反復中に匿名オブジェクトを生成しませんが、可能なコピー操作を必要とします(いくつかの注意として、状況によっては最適化されます。ただし、参照メソッドはコピーが生成されないことを保証します。 RVOを実行することは、できる限り最も効率的なコードをビルドしようとすることに代わるものではありません。

0
   vector<string> func1() const
   {
      vector<string> parts;
      return vector<string>(parts.begin(),parts.end()) ;
   } 
0
Amruth A