27

数値を取り、その数まで返す関数があります (たとえば、int)。最もクリーンなインターフェースは何ですか? いくつかの考え:

  1. を返しますvector<int>。ベクターは数回コピーされるため、非効率的です。
  2. を返しますvector<int>*。私のゲッターは、要素だけでなくベクター自体も割り当てる必要があります。誰がベクトルを解放しなければならないかという通常の問題がすべてあります。一度割り当てて、getter へのさまざまな呼び出しに同じストレージを使用することはできないという事実などです。これが、STL アルゴリズムが通常、メモリの割り当てを避け、代わりにそれは通過しました。
  3. を返しますunique_ptr<vector<int>>。誰がそれを削除したかは明らかですが、まだ他の問題があります。
  4. avector<int>を参照パラメーターとして使用します。ゲッター缶push_back()と呼び出し元はreserve()、スペースにするかどうかを決定できます。しかし、渡されたものvectorが空でない場合、getter は何をすべきでしょうか? 追加しますか?最初にクリアして上書きしますか?空だと主張しますか?関数のシグネチャが単一の解釈のみを許可しているとよいでしょう。
  5. beginandendイテレータを渡します。次に、実際に書き込まれたアイテムの数を返す必要があります (これは、必要な数よりも少ない可能性があります)。呼び出し元は、書き込まれたことのないアイテムにアクセスしないように注意する必要があります。
  6. ゲッターに を取得させiterator、呼び出し元は を渡すことができinsert_iteratorます。
  7. あきらめて、単に a を渡しますchar *。:)
4

4 に答える 4

37

C++11 では、標準コンテナーに対して移動セマンティクスがサポートされているため、オプション 1 を使用する必要があります

これにより、関数のシグネチャが明確になり、整数のベクトルが返されることだけが必要であり、コピーが発行されないため効率的になりますstd::vector。値の最適化が適用され、移動もコピーも行われません):

std::vector<int> foo()
{
    std::vector<int> v;
    // Fill in v...
    return v;
}

このようにして、所有権、不要な動的割り当て、および問題の単純さを損なうだけのその他の問題、つまり一連の整数を返すなどの問題に対処する必要がなくなります。

C++03 では、オプション 4 を使用して、ベクトル以外への左辺値参照を取得することをconst勧めします。C++03 の標準コンテナーは移動に対応しておらず、ベクトルのコピーにはコストがかかる場合があります。したがって:

void foo(std::vector<int>& v)
{
    // Fill in v...
}

ただし、その場合でも、このペナルティがユース ケースにとって本当に重要かどうかを検討する必要があります。そうでない場合は、いくつかの CPU サイクルを犠牲にして、より明確な関数シグネチャを選択することをお勧めします。

また、C++03 コンパイラは名前付き戻り値の最適化を実行できるため、理論的には戻り値から一時的なものをコピー構築する必要がありますが、実際にはコピーは行われない可能性があります。

于 2013-05-09T17:21:17.100 に答える
11

あなたはそれを自分で書きました:

...これが、STLアルゴリズムが通常、メモリの割り当てを避け、代わりに渡されるようにする理由です。

ただし、STL アルゴリズム通常「メモリを渡す」必要はなく、代わりにイテレータを操作します。これは特に、コンテナからアルゴリズムを分離するためのものであり、次の結果をもたらします。

オプション 8

入力イテレータを返すことにより、値の生成をそれらの値の使用と保存の両方から切り離します。

最も簡単な方法は を使用することboost::function_input_iteratorですが、スケッチ メカニズムを以下に示します (主に、考えるよりも速く入力していたためです)。


入力反復子の型

(C++11 を使用しますが、std::functionを関数ポインターに置き換えるか、生成ロジックをハードコーディングすることができます):

#include <functional>
#include <iterator>
template <typename T>
class Generator: public std::iterator<std::input_iterator_tag, T> {
    int count_;
    std::function<T()> generate_;
public:
    Generator() : count_(0) {}
    Generator(int count, std::function<T()> func) : count_(count)
                                                  , generate_(func) {}
    Generator(Generator const &other) : count_(other.count_)
                                      , generate_(other.generate_) {}
    // move, assignment etc. etc. omitted for brevity
    T operator*() { return generate_(); }
    Generator<T>& operator++() {
        --count_;
        return *this;
    }
    Generator<T> operator++(int) {
        Generator<T> tmp(*this);
        ++*this;
        return tmp;
    }
    bool operator==(Generator<T> const &other) const {
        return count_ == other.count_;
    }
    bool operator!=(Generator<T> const &other) const {
        return !(*this == other);
    }
};

ジェネレータ関数の例

(繰り返しになりますが、ラムダを C++98 の行外関数に置き換えるのは簡単ですが、これはタイピングが少なくて済みます)

#include <random>
Generator<int> begin_random_integers(int n) {
    static std::minstd_rand prng;
    static std::uniform_int_distribution<int> pdf;
    Generator<int> rv(n,
                      []() { return pdf(prng); }
                     );
    return rv;
}
Generator<int> end_random_integers() {
    return Generator<int>();
}

使用例

#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
    using namespace std;
    vector<int> out;

    cout << "copy 5 random ints into a vector\n";
    copy(begin_random_integers(5), end_random_integers(),
         back_inserter(out));
    copy(out.begin(), out.end(),
         ostream_iterator<int>(cout, ", "));

    cout << "\n" "print 2 random ints straight from generator\n";
    copy(begin_random_integers(2), end_random_integers(),
         ostream_iterator<int>(cout, ", "));

    cout << "\n" "reuse vector storage for 3 new ints\n";
    out.clear();
    copy(begin_random_integers(3), end_random_integers(),
         back_inserter(out));
    copy(out.begin(), out.end(),
         ostream_iterator<int>(cout, ", "));
}
于 2013-05-09T18:55:06.060 に答える
4

return vector<int>、コピーされず、移動されます。

于 2013-05-09T17:21:07.230 に答える
4

std::vector<int>C++11 では、明示的または暗黙的に移動されることを保証して、それを返すことが正しい答えです。(明示的な移動は一部の最適化をブロックする可能性があるため、暗黙的な移動を優先します)

面白いことに、バッファーの再利用が心配な場合、最も簡単な方法は、次のstd::vector<int>ような by 値を取るオプションのパラメーターをスローすることです。

std::vector<int> get_stuff( int how_many, std::vector<int> retval = std::vector<int>() ) {
  // blah blah
  return retval;
}

そして、適切なサイズの事前に割り当てられたバッファーがある場合は、それを関数に入れるだけで使用されますstd::moveget_stuff適切なサイズの事前割り当てバッファがない場合は、a を渡さないstd::vectorでください。

実際の例: http://ideone.com/quqnMQ

これが NRVO/RVO をブロックするかどうかはわかりませんが、そうするべき根本的な理由はありません。移動するのstd::vectorは十分に安価なので、とにかく NRVO/RVO をブロックするかどうか気にしないでしょう。

ただし、実際には a を返したくない場合がstd::vector<int>あります。おそらく、問題の要素を反復処理したいだけです。

その場合、簡単な方法と難しい方法があります。

for_each_element( Lambda )簡単な方法は、メソッドを公開することです。

#include <iostream>
struct Foo {
  int get_element(int i) const { return i*2+1; }
  template<typename Lambda>
  void for_each_element( int up_to, Lambda&& f ) {
    for (int i = 0; i < up_to; ++i ) {
      f( get_element(i) );
    }
  }
};
int main() {
  Foo foo;
  foo.for_each_element( 7, [&](int e){
    std::cout << e << "\n";
  });
}

std::functionの実装を非表示にする必要がある場合は、おそらく a を使用しますfor_each

困難な方法は、問題の要素を生成するジェネレータまたはイテレータのペアを返すことです。

これらはどちらも、要素を一度に 1 つずつ処理したい場合や、問題の値を生成するのにコストがかかる場合 (メモリのトラバースが必要になる場合がある場合) に、バッファの無意味な割り当てを回避します。

C++98 では、vector&and clear()it を使用します。

于 2013-05-09T17:58:03.530 に答える