web-dev-qa-db-ja.com

C ++でオブジェクトのリストを返す最良の方法は?

C++でプログラミングしてからしばらく経ちましたが、Pythonから来た後は、まっすぐなジャケットを着ているとすっごく感じます。

「パイプ」として機能する関数がいくつかあり、リストを入力として受け入れ、別のリストを出力として(入力に基づいて)返します。

これは概念上ですが、実際には、リストを表すために_std::vector_を使用していますが、それは許容できますか?

さらに、私はポインタを使用していないので、変数としてstd::vector<SomeType> the_list(some_size);を使用し、それを直接返します。つまり、_return the_list;_

P.S.これまでのところ、プロジェクトのサイズは小さく、パフォーマンスには影響しないようですが、pythonを書いているような気がするので、これについての入力/アドバイスを取得したいと思います。 C++。

22
hasen

私が見ることができる唯一のことは、あなたが返すリストのコピーを強制することです。次のようなことを行う方が効率的です。

  void DoSomething(const std::vector<SomeType>& in, std::vector<SomeType>& out)
  {
  ...
  // no need to return anything, just modify out
  }

返したいリストを渡すので、余分なコピーを避けます。

編集:これは古い返信です。移動セマンティクスを備えた最新のC++コンパイラーを使用できる場合は、これについて心配する必要はありません。もちろん、返されるオブジェクトに移動セマンティクスがない場合でも、この回答は当てはまります。

16
BigSandwich

本当に新しいリストが必要な場合は、単にそれを返します。ほとんどの場合、戻り値の最適化によって不要なコピーが処理されることはなく、コードは非常に明確に保たれます。
そうは言っても、リストを取得して他のリストを返すことは、確かにpython C++でのプログラミングです。

A、C++の場合、より適切なパラダイムは、一連のイテレーターを取り、基になるコレクションを変更する関数を作成することです。

例えば.

void DoSomething(iterator const & from, iterator const & to);

(必要に応じて、イテレータがテンプレートになる可能性があります)

連鎖操作は、begin()、end()で連続するメソッドを呼び出すことです。入力を変更したくない場合は、最初に自分でコピーを作成します。

std::vector theOutput(inputVector);

これはすべて、C++の「不要なものにお金を払わない」という哲学に基づいています。実際にオリジナルを保持したい場所にのみ、コピーを作成します。

13
Pieter

私は一般的なアプローチを使用します:

template <typename InIt, typename OutIt>
void DoMagic(InIt first, InIt last, OutIt out)
{
  for(; first != last; ++first) {
    if(IsCorrectIngredient(*first)) {
      *out = DoMoreMagic(*first);
      ++out;
    }
  }
}

今、あなたはそれを呼ぶことができます

std::vector<MagicIngredients> ingredients;
std::vector<MagicResults> result;

DoMagic(ingredients.begin(), ingredients.end(), std::back_inserter(results));

使用するアルゴリズムを変更せずに、使用するコンテナを簡単に変更できます。また、コンテナを返す際のオーバーヘッドがないので効率的です。

4
Ismael

本当にハードコアになりたい場合は、 boost :: Tuple を使用できます。

Tuple<int, int, double> add_multiply_divide(int a, int b) {
  return make_Tuple(a+b, a*b, double(a)/double(b));
}

しかし、すべてのオブジェクトが単一の非ポリモーフィック型であるように見えるため、std :: vectorはすべて問題なく機能します。タイプが多態性(基本クラスの継承クラス)である場合は、ポインターのベクトルが必要であり、ベクトルを破棄する前に、割り当てられたすべてのオブジェクトを削除することを忘れないでください。

1
Robert Gould

Std :: vectorを使用することは、多くの状況で好ましい方法です。連続メモリを使用することが保証されているため、L1キャッシュに適しています。

戻り値の型がstd :: vectorの場合に何が起こるかを知っておく必要があります。内部で発生するのは、std :: vectorが再帰的にコピーされるため、SomeTypeのコピーコンストラクターが高価な場合、「returnステートメント」は時間と時間のかかる操作になる可能性があります。

リストにたくさん検索して挿入している場合は、std :: setを調べて、線形ではなく対数時間計算量を取得できます。 (std :: vectors insertは、その容量を超えるまで一定です)。

あなたは多くの「パイプ関数」を持っていると言っています... std :: transformの優れたシナリオのように聞こえます。

1
Schildmeijer

オブジェクトのリストを返す際のもう1つの問題は(BigSandwichが指摘したように、1つまたは2つのリストで作業するのではなく)、オブジェクトに複雑なコピーコンストラクターがある場合、コンテナー内の各要素に対して呼び出されることです。

それぞれがメモリの塊を参照する1000個のオブジェクトがあり、それらがそのメモリをオブジェクトa、bにコピーする場合。 a = b;コンテナに入れて返送するだけで、1000枚のメンコピーになります。それでもコンテナを直接返したい場合は、この場合のポインタについて考えてください。

0
heeen

それは非常に簡単に動作します。

list<int> foo(void)
{
    list<int> l; 
    // do something
    return l;
}

現在データを受信して​​います:

list<int> lst=foo();

コンパイラはlstのコンストラクタをうまく最適化することを知っているので、完全に最適です。コピーは発生しません。

他の方法、よりポータブル:

list<int> lst;
// do anything you want with list
lst.swap(foo());

何が起こるか:fooはすでに最適化されているので、値を返すのに問題はありません。 swapを呼び出すときは、lstの値をnewに設定するため、コピーしないでください。これで、lstの古い値が「スワップ」され、破棄されます。

これは仕事をするための効率的な方法です。

0
artyom