visual para optimizar lista lentas guia consultas codigos botones c++ iterator generator coroutine yield-keyword

c++ - para - lista de codigos visual basic



Palabra clave de "rendimiento" para C++, ¿Cómo devolver un iterador desde mi función? (4)

Cuando analiza algo recursivamente o cuando el procesamiento tiene estados, el patrón del generador podría ser una buena idea y simplificar el código en gran medida, entonces uno no puede iterar fácilmente, y normalmente las devoluciones de llamada son la alternativa. Quiero tener yield y descubrir que Boost.Coroutine2 parece bueno de usar ahora.

El siguiente código es un ejemplo de archivos cat . Por supuesto, no tiene sentido, hasta el punto en el que desea procesar aún más las líneas de texto:

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

Considere el siguiente código.

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

Mientras pensaba si podría o no esperar que la Optimización del Valor de Retorno (RVO) sucediera, encontré esta respuesta de Jerry Coffin [énfasis mío]:

Al menos en la OMI, suele ser una mala idea, pero no por razones de eficiencia. Es una mala idea porque la función en cuestión generalmente debe escribirse como un algoritmo genérico que produce su salida a través de un iterador. Casi cualquier código que acepte o devuelva un contenedor en lugar de operar en iteradores debe considerarse sospechoso.

No me malinterpretes: hay ocasiones en las que tiene sentido pasar objetos similares a colecciones (por ejemplo, cadenas), pero para el ejemplo citado, consideraría que pasar o devolver al vector es una mala idea.

Teniendo algunos antecedentes de Python, me gustan mucho los generadores. En realidad, si se tratara de Python, habría escrito la función anterior como un Generador, es decir, para evitar la necesidad de procesar toda la información antes de que pudiera ocurrir cualquier otra cosa. Por ejemplo como este:

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

Si interpreté correctamente la nota de Jerry Coffins, esto es lo que sugirió, ¿no es así? Si es así, ¿cómo puedo implementar esto en C ++?


Descubrí que un comportamiento parecido a una corriente se acercaría a lo que tenía en mente. Considere el siguiente código (no probado):

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

Ahora puedo hacerlo como muestra el siguiente ejemplo:

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

De esta manera, la adquisición de datos se desacopla de alguna manera del procesamiento y, por lo tanto, se permite que se realice de forma perezosa / asíncrona. Es decir, podría procesar los elementos a medida que llegan y no tengo que esperar hasta que el vector se llene completamente como en el otro ejemplo.


Hay una publicación en el blog del autor de Boost.Asio, Chris Kohlhoff, sobre este tema: http://blog.think-async.com/2009/08/secret-sauce-revealed.html

Simula el yield con una macro.

#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

Esto tiene que ser usado en conjunto con una clase de coroutine . Vea el blog para más detalles.


No, eso no es lo que Jerry quiere decir, al menos no directamente.

yield en Python implementa coroutines . C ++ no los tiene (pero, por supuesto, pueden ser emulados, pero eso es un poco complicado si se hace de manera limpia).

Pero lo que Jerry quiso decir es que debes pasar un iterador de salida que luego se escribe a:

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

Y llámalo:

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

No estoy convencido de que esto sea generalmente mejor que simplemente devolver el vector.