Solución de algoritmo:
std::generate(numbers.begin(), numbers.end(), rand);
Solución for-loop basada en rango:
for (int& x : numbers) x = rand();
¿Por qué querría usar los std::generate
bucles for más detallados que los basados en rangos en C ++ 11?
begin()
yend()
?range
función en su caja de herramientas. (iefor(auto& x : range(first, last))
)boost::generate(numbers, rand); // ♪
Respuestas:
La primera versión
std::generate(numbers.begin(), numbers.end(), rand);
nos dice que desea generar una secuencia de valores.
En la segunda versión, el lector tendrá que averiguarlo por sí mismo.
Por lo general, ahorrar escribiendo no es óptimo, ya que la mayoría de las veces se pierde en el tiempo de lectura. La mayor parte del código se lee mucho más de lo que se escribe.
fuente
numbers
dos veces sin ningún motivo. Por lo tanto:boost::range::generate(numbers, rand);
. No hay ninguna razón por la que no pueda tener un código más corto y más legible en una biblioteca bien construida.std::generate(number.begin(), numbers.begin()+3, rand)
hacerlo, ¿no es así? Así que supongo que especificarnumber
dos veces puede resultar útil a veces.std::generate()
, puede hacerlostd::generate(slice(number.begin(), 3), rand)
o incluso mejor con una sintaxis hipotética de corte de rango como lastd::generate(number[0:3], rand)
que elimina la repetición denumber
mientras aún permite la especificación flexible de parte del rango. Hacer lo contrario a partir de un argumento de tresstd::generate()
es más tedioso.Si el ciclo for está basado en rango o no, no hace ninguna diferencia, solo simplifica el código dentro del paréntesis. Los algoritmos son más claros porque muestran la intención .
fuente
Personalmente, mi lectura inicial de:
std::generate(numbers.begin(), numbers.end(), rand);
es "estamos asignando a todo en un rango. El rango es
numbers
. Los valores asignados son aleatorios".Mi lectura inicial de:
for (int& x : numbers) x = rand();
es "estamos haciendo algo con todo en un rango. El rango es
numbers
. Lo que hacemos es asignar un valor aleatorio".Son bastante similares, pero no idénticos. Una razón plausible por la que podría querer provocar la primera lectura es porque creo que el hecho más importante de este código es que se asigna al rango. Así que ahí está su "por qué querría ...". Lo uso
generate
porque en C ++std::generate
significa "asignación de rango". Como lo hace por ciertostd::copy
, la diferencia entre los dos es desde qué estás asignando.Sin embargo, existen factores de confusión. Los bucles for basados en rango tienen una forma inherentemente más directa de expresar que el rango es
numbers
, que los algoritmos basados en iteradores. Es por eso que la gente trabaja en bibliotecas de algoritmos basados en rangos: seboost::range::generate(numbers, rand);
ve mejor que lastd::generate
versión.En contra de eso,
int&
en su bucle for basado en rango hay una arruga. ¿Qué pasa si el tipo de valor del rango no lo esint
, entonces estamos haciendo algo molestamente sutil aquí que depende de que sea convertible aint&
, mientras que elgenerate
código solo depende del retorno derand
ser asignable al elemento? Incluso si el tipo de valor esint
, podría detenerme a pensar si lo es o no. Porauto
lo tanto , lo que pospone pensar en los tipos hasta que veo lo que se asigna - conauto &x
digo "tome una referencia al elemento de rango, sea cual sea el tipo que pueda tener". En C ++ 03, los algoritmos (porque son plantillas de funciones) eran la forma de ocultar tipos exactos, ahora son una forma.Creo que siempre ha sido el caso de que los algoritmos más simples tienen solo un beneficio marginal sobre los bucles equivalentes. Los bucles for basados en rangos mejoran los bucles (principalmente al eliminar la mayor parte del texto estándar, aunque hay un poco más que eso). Por lo tanto, los márgenes se reducen y quizás cambie de opinión en algunos casos específicos. Pero todavía hay una diferencia de estilo allí.
fuente
operator int&()
? :)int&
conSomeClass&
y ahora tienes que preocuparte por los operadores de conversión y los constructores de un solo parámetro no marcadosexplicit
.operator int&()
yoperator int const &() const
, pero de nuevo podría funcionar sobrecargandooperator int() const
yoperator=(int)
.En mi opinión, el artículo 43 de STL eficaz: "Prefiero las llamadas de algoritmos a los bucles escritos a mano". sigue siendo un buen consejo.
Normalmente escribo funciones contenedoras para deshacerme del
begin()
/end()
hell. Si lo hace, su ejemplo se verá así:Creo que supera el rango basado en bucle for tanto en la comunicación de la intención como en la legibilidad.
Habiendo dicho eso, debo admitir que en C ++ 98 algunas llamadas de algoritmos STL produjeron código indecible y seguir "Preferir llamadas de algoritmos a bucles escritos a mano" no parecía una buena idea. Afortunadamente, las lambdas han cambiado eso.
Considere el siguiente ejemplo de Herb Sutter: Lambdas, Lambdas Everywhere .
Tarea: busque el primer elemento en v que sea
> x
y< y
.Sin lambdas:
auto i = find_if( v.begin(), v.end(), bind( logical_and<bool>(), bind(greater<int>(), _1, x), bind(less<int>(), _1, y) ) );
Con lambda
auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );
fuente
En mi opinión, el ciclo manual, aunque podría reducir la verbosidad, carece de forma legible:
for (int& x : numbers) x = rand();
No usaría este ciclo para inicializar 1 el rango definido por números , porque cuando lo miro, me parece que está iterando sobre un rango de números, pero en realidad no lo hace (en esencia), es decir, en lugar de leyendo del rango, está escribiendo en el rango.
La intención es mucho más clara cuando se usa
std::generate
.1. Inicializar en este contexto significa dar un valor significativo a los elementos del contenedor.
fuente
std::generate
, lo que se puede asumir de un programador de C ++ (si no están familiarizados, lo buscarán, el mismo resultado).&
carácter?) La ventaja de los algoritmos es que muestran intención, mientras que con los bucles tienes que inferir eso. Si hay un error en la implementación del ciclo, no está claro si es un error o fue intencional.std::generate
, con solo mirar, se pueda decir que esta función está generando algo ; lo que ese algo es respondido por el tercer argumento de la función. Creo que esto es mucho mejor.Hay algunas cosas que no puede hacer (simplemente) con bucles basados en rangos que los algoritmos que toman iteradores como entrada pueden. Por ejemplo con
std::generate
:Llene el contenedor hasta
limit
(excluido,limit
es un iterador válido activadonumbers
) con variables de una distribución y el resto con variables de otra distribución.std::generate(numbers.begin(), limit, rand1); std::generate(limit, numbers.end(), rand2);
Los algoritmos basados en iteradores le brindan un mejor control sobre el rango en el que está operando.
fuente
Para el caso particular de
std::generate
, estoy de acuerdo con las respuestas anteriores sobre el problema de legibilidad / intención. std :: generate me parece una versión más clara. Pero admito que esto es en cierto modo una cuestión de gustos.Dicho esto, tengo otra razón para no desechar el algoritmo std ::: hay ciertos algoritmos que están especializados para algunos tipos de datos.
El ejemplo más simple sería
std::fill
. La versión general se implementa como un bucle for sobre el rango proporcionado, y esta versión se utilizará al crear instancias de la plantilla. Pero no siempre. Por ejemplo, si le proporciona un rango que es unstd::vector<int>
- a menudo llamarámemset
bajo el capó, produciendo un código mucho más rápido y mejor.Así que estoy tratando de jugar una carta de eficiencia aquí.
Su bucle escrito a mano puede ser tan rápido como una versión del algoritmo std ::, pero difícilmente puede ser más rápido. Y más que eso, el algoritmo std :: puede estar especializado para contenedores y tipos particulares y se hace bajo la interfaz limpia de STL.
fuente
Mi respuesta sería tal vez y no. Si estamos hablando de C ++ 11, entonces tal vez (más como no). Por ejemplo,
std::for_each
es realmente molesto usarlo incluso con lambdas:std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x) { // do stuff with x });
Pero usar basado en rango para es mucho mejor:
for (auto& x : c) { // do stuff with x }
Por otro lado, si estamos hablando de C ++ 1y, entonces diría que no, los algoritmos no quedarán obsoletos por rango basado en. En el comité estándar de C ++ hay un grupo de estudio que está trabajando en una propuesta para agregar rangos a C ++, y también se está trabajando en lambdas polimórficas. Los rangos eliminarían la necesidad de usar un par de iteradores y lambda polimórfica le permitiría no especificar el tipo de argumento exacto de lambda. Esto significa que
std::for_each
podría usarse así (no lo tome como un hecho difícil, así es como se ven los sueños hoy):std::for_each(c.range(), [](x) { // do stuff with x });
fuente
[]
con la lambda especificas captura cero? Es decir, en comparación con simplemente escribir un cuerpo de bucle, ha aislado un fragmento de código del contexto de búsqueda de variables en el que aparece léxicamente. El aislamiento suele ser útil para el lector, menos en qué pensar mientras lee.for_each
todavía no tiene sentido incluso cuando se usa con una lambda. foreach + capturar lambda es actualmente una forma detallada de escribir un bucle for basado en rangos, y se vuelve un poco menos detallado pero aún más que el bucle. No es que crea que debas defenderfor_each
, por supuesto, pero incluso antes de ver tu respuesta, estaba pensando que si el interrogador quería vencer a los algoritmos, podría haber elegidofor_each
como el más suave de todos los objetivos posibles ;-)for_each
, pero tiene una pequeña ventaja sobre el rango basado en: puede hacer que sea paralelo más fácilmente simplemente prefijándolo con paralelo_ para convertirlo enparallel_for_each
(si usa PPL, y asumiendo que es seguro para subprocesos hacerlo) . :-Dstd::algorithm
implementación de s está oculta detrás de su interfaz y puede ser arbitrariamente compleja (u optimizada arbitrariamente).Una cosa que debe notarse es que un algoritmo expresa lo que se hace, no cómo.
El ciclo basado en rango incluye la forma en que se hacen las cosas: comience con el primero, aplique y continúe con el siguiente elemento hasta el final. Incluso un algoritmo simple podría hacer las cosas de manera diferente (al menos algunas sobrecargas para contenedores específicos, ni siquiera pensar en el vector horrible), y al menos la forma en que se hace no es el negocio del escritor.
Para mí, esa es gran parte de la diferencia, encapsula todo lo que puedas, y eso justifica la oración cuando puedas, usa algoritmos.
fuente
El bucle for basado en rango es solo eso. Hasta que, por supuesto, se cambie el estándar.
El algoritmo es una función. Una función que impone algunos requisitos a sus parámetros. Los requisitos están redactados en un estándar para permitir, por ejemplo, una implementación que aproveche todos los subprocesos de ejecución disponibles y lo acelerará automáticamente.
fuente