【问题标题】:"yield" keyword for C++, How to Return an Iterator from my Function?C++ 的“yield”关键字,如何从我的函数返回迭代器?
【发布时间】:2012-08-07 13:54:36
【问题描述】:

考虑下面的代码。

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 背景,我非常喜欢生成器。实际上,如果它是 Python,我会将上面的函数编写为生成器,即避免在其他任何事情发生之前处理整个数据的必要性。比如这样:

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

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

【问题讨论】:

  • 只返回向量,完全没问题。 (N)RVO 很可能会处理这个问题,并且在 C++11 中,移动语义将在 (N)RVO 不处理时执行。另外,return process_data(get_data_from_database());。遗憾的是,C++ 没有yield 功能。 :(
  • “几乎所有接受或返回容器而不是在迭代器上操作的代码都应该被认为是可疑的。”我会反对这一点。这当然是一个很好的一般建议,但是让代码独立于容器类型的愿望经常被误导,而且它们除了语法之外不是很容易互换......

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


【解决方案1】:

不,这不是 Jerry 的意思,至少不是直接的意思。

yield 在 Python 中实现了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));

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

【讨论】:

  • 支持传递“输出”迭代器的一大优势是代码在调用process_data 两次时变得更高效 - 可能在不同的输入数据上 - 但想要输出在同一个result 对象中。效率是您首先使用 C++ 的原因,对吧?除此之外,您允许调用者为容器使用自定义分配器。
  • @Frerich 我承认这是一个应该考虑的有效点。也许 Jerry 的建议比我说的更普遍适用。
  • 返回迭代器的另一个好处是你不会强迫调用者使用你决定的数据结构。例如,result 可以是一个集合。
【解决方案2】:

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 类结合使用。有关详细信息,请参阅博客。

【讨论】:

    【解决方案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;
        }
    }
    

    【讨论】:

    • 看起来很有趣!不幸的是,我仍然坚持使用 boost 版本 1.58 (Coroutine2 is available since v1.59)。但我稍后会尝试。感谢分享!
    • @moooeeeep 你可以先试试 boost::coroutine。它应该与更多的编译器兼容,但您不能在内容上使用基于范围的 C++11 for 循环。你必须这样写: while (seq) { cout
    【解决方案4】:

    我发现类似 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;
        }
    }
    

    通过这种方式,数据采集以某种方式与处理解耦,从而允许延迟/异步发生。也就是说,我可以在物品到达时对其进行处理,而不必像在另一个示例中那样等到矢量完全填充。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-02
      • 1970-01-01
      • 2022-11-02
      相关资源
      最近更新 更多