20

次のコードを検討してください。

std::vector<result_data> do_processing() 
{
    pqxx::result input_data = get_data_from_database();
    return process_data(input_data);
}

std::vector<result_data> process_data(pqxx::result const & input_data)
{
    std::vector<result_data> ret;
    pqxx::result::const_iterator row;
    for (row = input_data.begin(); row != inpupt_data.end(); ++row) 
    {
        // somehow populate output vector
    }
    return ret;
}

リターンバリュー最適化(RVO)が発生することを期待できるかどうかを考えていたときに、JerryCoffin [強調鉱山]によるこの回答を見つけました。

少なくともIMOは、通常は悪い考えですが、効率上の理由からではありません。問題の関数は通常、イテレータを介して出力を生成する汎用アルゴリズムとして記述される必要があるため、これはお勧めできません。イテレータを操作する代わりにコンテナを受け入れるか返すほとんどすべてのコードは、疑わしいと見なされます。

誤解しないでください。コレクションのようなオブジェクト(文字列など)を渡すのが理にかなっている場合もありますが、引用した例では、ベクトルを渡すか返すのはお勧めできません。

Pythonのバックグラウンドを持っているので、ジェネレーターがとても好きです。実際、Pythonの場合、上記の関数をジェネレーターとして記述します。つまり、他の何かが発生する前にデータ全体を処理する必要がないようにします。たとえば、次のようになります。

def process_data(input_data):
    for item in input_data:
        # somehow process items
        yield result_data

私がジェリー・コフィンズのメモを正しく解釈した場合、これは彼が提案したことですよね?もしそうなら、どうすればこれをC ++で実装できますか?

4

4 に答える 4

17

いいえ、それはジェリーが意味することではありません、少なくとも直接ではありません。

yieldPythonではコルーチンを実装しています。C ++にはそれらがありません(ただし、もちろんエミュレートすることはできますが、きれいに実行すると少し複雑になります)。

しかし、ジェリーが意味したのは、出力イテレータを渡して、次のように書き込む必要があるということです。

template <typename O>
void process_data(pqxx::result const & input_data, O iter) {
    for (row = input_data.begin(); row != inpupt_data.end(); ++row)
        *iter++ = some_value;
}

そしてそれを呼んでください:

std::vector<result_data> result;
process_data(input, std::back_inserter(result));

これは単にベクトルを返すよりも一般的に優れているとは思いませんが。

于 2012-08-10T09:26:51.227 に答える
12

Boost.Asioの作者であるChrisKohlhoffによるブログ投稿があります:http://blog.think-async.com/2009/08/secret-sauce-revealed.html

yield彼はマクロでシミュレートします

#define yield \
  if ((_coro_value = __LINE__) == 0) \
  { \
    case __LINE__: ; \
    (void)&you_forgot_to_add_the_entry_label; \
  } \
  else \
    for (bool _coro_bool = false;; \
         _coro_bool = !_coro_bool) \
      if (_coro_bool) \
        goto bail_out_of_coroutine; \
      else

coroutineこれは、クラスと組み合わせて使用​​する必要があります。詳細については、ブログを参照してください。

于 2012-08-10T09:24:20.277 に答える
3

何かを再帰的に解析する場合、または処理に状態がある場合、ジェネレーターパターンは良いアイデアであり、コードを大幅に単純化する可能性があります。その場合、簡単に反復することはできず、通常はコールバックが代わりになります。持っていたいのですがyieldBoost.Coroutine2を使用するのが良いようです。

以下のコードはcatファイルの例です。もちろん、テキスト行をさらに処理するまでは意味がありません。

#include <fstream>
#include <functional>
#include <iostream>
#include <string>
#include <boost/coroutine2/all.hpp>

using namespace std;

typedef boost::coroutines2::coroutine<const string&> coro_t;

void cat(coro_t::push_type& yield, int argc, char* argv[])
{
    for (int i = 1; i < argc; ++i) {
        ifstream ifs(argv[i]);
        for (;;) {
            string line;
            if (getline(ifs, line)) {
                yield(line);
            } else {
                break;
            }
        }
    }
}

int main(int argc, char* argv[])
{
    using namespace std::placeholders;
    coro_t::pull_type seq(
            boost::coroutines2::fixedsize_stack(),
            bind(cat, _1, argc, argv));
    for (auto& line : seq) {
        cout << line << endl;
    }
}
于 2016-08-06T09:12:20.230 に答える
0

私は、istreamのような振る舞いが私が考えていたものに近づくことを発見しました。次の(テストされていない)コードについて考えてみます。

struct data_source {
public:
    // for delivering data items
    data_source& operator>>(input_data_t & i) {
        i = input_data.front(); 
        input_data.pop_front(); 
        return *this; 
    }
    // for boolean evaluation
    operator void*() { return input_data.empty() ? 0 : this; }

private:
    std::deque<input_data_t> input_data;

    // appends new data to private input_data
    // potentially asynchronously
    void get_data_from_database();
};

これで、次の例に示すように実行できます。

int main () {
    data_source d;
    input_data_t i;
    while (d >> i) {
        // somehow process items
        result_data_t r(i);
        cout << r << endl;
    }
}

このようにして、データ取得は処理から何らかの形で切り離され、それによって遅延/非同期で発生することが可能になります。つまり、アイテムが到着したときに処理することができ、他の例のようにベクトルが完全に満たされるまで待つ必要はありません。

于 2012-09-15T10:54:43.410 に答える