簡體   English   中英

C ++的“yield”關鍵字,如何從函數中返回迭代器?

[英]“yield” keyword for C++, How to Return an Iterator from my Function?

請考慮以下代碼。

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)時,我發現Jerry Coffin的這個答案 [強調我的]:

至少IMO,這通常是一個糟糕的主意,但不是出於效率原因。 這是一個糟糕的主意,因為有問題的函數通常應該寫成通過迭代器生成輸出的通用算法 幾乎任何接受或返回容器而不是在迭代器上運行的代碼都應該被認為是可疑的。

不要誤會我的意思:有時候傳遞類似集合的對象(例如字符串)是有意義的,但對於引用的例子,我會考慮傳遞或返回向量一個糟糕的想法。

有一些Python背景,我非常喜歡Generators。 實際上,如果它是Python,我會把上面的函數寫成一個Generator,即避免在其他任何事情發生之前處理整個數據的必要性。 例如這樣:

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

如果我正確地解釋了Jerry Coffins的說法,這就是他的建議,不是嗎? 如果是這樣,我如何在C ++中實現它?

不,這不是傑瑞的意思,至少不是直接的。

Python中的yield實現了協同程序 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));

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

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類一起使用。 有關詳細信息,請參閱博客。

當你遞歸地解析某些東西或者當處理有狀態時,生成器模式可能是一個好主意,並且大大簡化了代碼 - 一個人不能輕易迭代,通常回調是​​另一種選擇。 我想得到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;
    }
}

我發現一種類似於夢想的行為會接近我的想法。 考慮以下(未經測試的)代碼:

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

這樣,數據采集以某種方式與處理分離,從而允許懶惰/異步發生。 也就是說,我可以在它們到達時處理這些項目,而不必像其他示例那樣等待向量完全填充。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM