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


Me encuentro utilizando C ++ 11 más y más últimamente, y donde habría estado usando iteradores en el pasado, ahora estoy usando basado en rango para bucles cuando 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 era 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).


32
2017-10-24 20:55


origen


Respuestas:


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 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 un no-const referencia. Esto se puede evitar si es necesario usando un const versión de coll.


13
2017-10-24 21:13



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   rango basado en la declaración 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 del significado de todo eso __ gubbins).

El estándar no hace ningún garantías ya sea esa línea const auto &e = *__begin introduce una sobrecarga de rendimiento, por supuesto, en comparación con el uso directo *__begin en lugar de e dentro declaración. 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 debería haber una sobrecarga en un compilador sensible, en el caso donde __begines un iterador contenedor (cuyo operator* devuelve una referencia), y luego e se pasa por valor en declaración.


11
2017-10-24 21:16