C ++的“yield”关键字,如何从函数中返回迭代器?

时间:2012-08-10 09:17:31

标签: c++ iterator generator coroutine yield-keyword

请考虑以下代码。

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)时,我发现this answer by Jerry Coffin [强调我的]:

  

至少IMO,它通常是一个糟糕的主意,但不是出于效率原因。这是一个糟糕的主意,因为所讨论的函数通常应该写成通过迭代器生成其输出的通用算法几乎所有接受或返回容器而不是在迭代器上运行的代码都应该被认为是可疑的。

     

不要误会我的意思:有时候传递类似集合的对象(例如字符串)是有意义的,但是对于引用的例子,我考虑传递或返回向量是一个糟糕的主意

有一些Python背景,我非常喜欢Generators。实际上,如果它是Python,我会把上面的函数写成一个生成器,即避免在其他任何事情发生之前处理整个数据的必要性。例如:

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

如果我正确地解释了Jerry Coffins的说明,这就是他的建议,不是吗?如果是这样,我如何在C ++中实现它?

4 个答案:

答案 0 :(得分:16)

不,这不是杰瑞所说的,至少不是直接的。

Python中的

yield实现了coroutines。 C ++没有它们(但它们当然可以被模拟,但如果干净利落就有点参与其中)。

但Jerry的意思只是你应该传入一个输出迭代器,然后写入:

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));

我不相信这通常比返回矢量更好。

答案 1 :(得分:12)

Boost.Asio作者Chris Kohlhoff发表了一篇博文: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类一起使用。有关详细信息,请参阅博客。

答案 2 :(得分:3)

当你递归地解析某些东西或者当处理有状态时,生成器模式可能是一个好主意并且大大简化了代码 - 一个人不能轻易迭代,通常回调是​​替代方案。我希望yield,并发现Boost.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;
    }
}

答案 3 :(得分:0)

我发现类似于is的行为会接近我的想法。请考虑以下(未经测试的)代码:

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;
    }
}

这样,数据采集以某种方式与处理分离,从而允许延迟/异步发生。也就是说,我可以在它们到达时处理这些项目,而不必像其他示例那样等待向量完全填充。