Подтвердить что ты не робот

Ключевое слово "yield" для С++, как вернуть Iterator из моей функции?

Рассмотрим следующий код.

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) произойдет, я нашел этот ответ Джерри Коффина [акцент мой]:

По крайней мере, ИМО, это обычно плохая идея, но не по соображениям эффективности. Это плохая идея, потому что рассматриваемая функция обычно должна быть написана как общий алгоритм , который выдает свой результат через итератор. Почти любой код, который принимает или возвращает контейнер вместо работы на итераторах, должен считаться подозрительным.

Не поймите меня неправильно: бывают времена, когда имеет смысл передавать объекты, подобные друг другу (например, строки), но в приведенном примере я бы рассмотрел передачу или возвращение вектора в плохую идею.

Имея некоторый фон Python, мне очень нравятся генераторы. На самом деле, если бы это был Python, я бы написал выше функцию в качестве генератора, т.е. Чтобы избежать необходимости обработки всех данных до того, как что-либо еще может произойти. Например, например:

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

Если я правильно интерпретирую слова Джерри Коффина, это то, что он предложил, не так ли? Если да, как это реализовать на С++?

4b9b3361

Ответ 1

Нет, это не то, что Джерри означает, по крайней мере, не напрямую.

yield в Python реализует сопрограммы. С++ не имеет их (но они, конечно, могут быть эмулированы, но это немного связано, если сделано чисто).

Но то, что имел в виду Джерри, - это просто, что вы должны передать выходной итератор, который затем записывается в:

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

Im не убежден, что это вообще лучше, чем просто возврат вектора.

Ответ 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;
    }
}

Ответ 4

Я обнаружил, что подобное поведение похоже на то, что я имел в виду. Рассмотрим следующий (непроверенный) код:

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

Таким образом, сбор данных каким-то образом отделен от обработки и, таким образом, может случиться лениво/асинхронно. То есть я мог обрабатывать элементы по мере их поступления, и мне не нужно ждать, пока вектор будет заполнен полностью, как в другом примере.