iterators iteradores iterador for example c++ templates c++11

c++ - for - iteradores model builder



¿Alguna desventaja de usar referencia constante cuando se itera sobre tipos básicos? (2)

Cada vez estoy usando C ++ 11 más y más, y donde habría estado usando iteradores en el pasado, ahora estoy usando bucles basados ​​en rangos siempre que sea posible:

std::vector<int> coll(10); std::generate(coll.begin(), coll.end(), []() { return rand(); } );

C ++ 03:

for (std::vector<int>::const_iterator it = coll.begin(); it != coll.end(); ++it) { foo_func(*it); }

C ++ 11:

for (auto e : coll) { foo_func(e); }

Pero, ¿y si el tipo de elemento de colección es un parámetro de plantilla? foo_func() probablemente estará sobrecargado para pasar tipos complejos (= costosos de copiar) por referencia constante, y los simples por valor:

foo_func(const BigType& e) { ... }; foo_func(int e) { ... };

No pensé tanto mientras usaba el código de C ++ 03 anterior. Yo iteraría de la misma manera, y desde la eliminación de referencias a un const_iterator produce una referencia constante, todo estaba bien. Pero usando el bucle for de C ++ 11, necesito usar una variable de bucle de referencia const para obtener el mismo comportamiento:

for (const auto& e : coll) { foo_func(e); }

Y de repente ya no estaba seguro, si esto no introduciría instrucciones de ensamblaje innecesarias si auto fuera un tipo simple (como un puntero detrás de la escena para implementar la referencia).

Pero la compilación de una aplicación de muestra confirmó que no hay sobrecarga para los tipos simples, y que esta parece ser la forma genérica de usar bucles basados ​​en rangos para las plantillas. Si este no hubiera sido el caso, boost::call_traits::param_type habría sido el camino a seguir.

Pregunta: ¿Hay alguna garantía en el estándar?

(Me doy cuenta de que el problema no está realmente relacionado con los bucles for range-based. También está presente cuando se usan const_iterators).


6.5.4 / 1 dice:

for ( for-range-declaration : braced-init-list ) statement

permita que range-init sea equivalente a la lista inicial de braced. En cada caso, un enunciado basado en rango es equivalente a

{ auto && __range = range-init; for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } }

(Sigue la explicación de los significados de todos los __ gubbins).

El estándar no garantiza si la línea const auto &e = *__begin introduce una sobrecarga de rendimiento, por supuesto, en comparación con el uso directo de *__begin lugar de e inside statement . Las implementaciones pueden implementar referencias copiando laboriosamente un puntero en una ranura de pila y luego volviéndola a leer cada vez que se utiliza la referencia, y no es necesario optimizarlas.

Pero no hay ninguna razón por la cual deba haber una sobrecarga en un compilador sensible, en el caso donde __begin es un iterador de contenedor (cuyo operator* devuelve una referencia), y luego e se pasa por valor en la instrucción .


Todos los contenedores estándar devuelven referencias de su iterador (tenga en cuenta, sin embargo, que algunos "contenedores no son realmente contenedores, por ejemplo, std::vector<bool> que devuelve un proxy). Otros iteradores pueden devolver proxies o valores aunque esto no es así '' t estrictamente compatible.

Por supuesto, el estándar no ofrece ninguna garantía con respecto al rendimiento. Cualquier tipo de característica relacionada con el rendimiento (más allá de las garantías de complejidad) se considera calidad de implementación.

Dicho esto, es posible que desee considerar hacer que el compilador elija usted como lo hizo antes:

for (auto&& e: coll) { f(e); }

El problema principal aquí es que f() puede recibir una referencia no const . Esto se puede evitar si es necesario utilizando una versión de coll de const .