ventajas programar programacion mas lenguajes lenguaje futuro desventajas con c++ performance c++11 auto

programar - ¿Puede el uso del ''auto'' de C++ 11 mejorar el rendimiento?



programar en go (4)

Como auto deduce el tipo de la expresión de inicialización, no hay conversión de tipo involucrada. Combinado con algoritmos con plantilla, esto significa que puede obtener un cálculo más directo que si fuera a inventar un tipo usted mismo, ¡especialmente cuando se trata de expresiones cuyo tipo no puede nombrar!

Un ejemplo típico proviene de (ab) usando std::function :

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1); // bad auto cmp2 = std::bind(f, _2, 10, _1); // good auto cmp3 = [](T a, T b){ return f(b, 10, a); }; // also good std::stable_partition(begin(x), end(x), cmp?);

Con cmp2 y cmp3 , todo el algoritmo puede alinear la llamada de comparación, mientras que si construye un objeto std::function , no solo la llamada no puede estar en línea, sino que también tiene que pasar por la búsqueda polimórfica en el interior de tipo borrado del contenedor de funciones.

Otra variante de este tema es que puedes decir:

auto && f = MakeAThing();

Esto siempre es una referencia, vinculada al valor de la expresión de llamada de función, y nunca construye ningún objeto adicional. Si no conocía el tipo del valor devuelto, es posible que se vea obligado a construir un nuevo objeto (tal vez como temporal) a través de algo como T && f = MakeAThing() . (Además, auto && incluso funciona cuando el tipo de retorno no es móvil y el valor de retorno es un valor privalente).

Puedo ver por qué el tipo auto en C ++ 11 mejora la corrección y la facilidad de mantenimiento. He leído que también puede mejorar el rendimiento ( Casi siempre automático de Herb Sutter), pero echo de menos una buena explicación.

  • ¿Cómo puede mejorar auto rendimiento?
  • ¿Alguien puede dar un ejemplo?

Hay dos categorias.

auto puede evitar el borrado de tipo. Hay tipos no innombrables (como lambdas), y tipos casi innombrables (como el resultado de std::bind u otras plantillas de expresiones similares).

Sin auto , termina teniendo que borrar los datos a algo como std::function . El borrado de tipo tiene costos.

std::function<void()> task1 = []{std::cout << "hello";}; auto task2 = []{std::cout << " world/n";};

task1 tiene sobrecarga de borrado de tipo: una posible asignación de montón, dificultad para incluirla y sobrecarga de invocación de la tabla de funciones virtuales. task2 no tiene ninguno. Las lambdas necesitan deducción automática u otras formas de deducción de tipo para almacenar sin borrado de tipo; otros tipos pueden ser tan complejos que solo lo necesitan en la práctica.

En segundo lugar, puede obtener tipos incorrectos. En algunos casos, el tipo incorrecto funcionará aparentemente perfectamente, pero causará una copia.

Foo const& f = expression();

se compilará si la expression() devuelve Bar const& o Bar o incluso Bar& , donde Foo se puede construir a partir de Bar . Se creará un Foo temporal, luego se unirá a f , y su vida útil se extenderá hasta que f desaparezca.

El programador puede haber querido decir Bar const& f y no tiene la intención de hacer una copia allí, pero se hace una copia independientemente.

El ejemplo más común es el tipo de *std::map<A,B>::const_iterator , que es std::pair<A const, B> const& not std::pair<A,B> const& , pero el error es una categoría de errores que silenciosamente cuestan rendimiento. Puede construir un std::pair<A, B> partir de un std::pair<const A, B> . (La clave en un mapa es constante, porque editarlo es una mala idea)

Tanto @Barry como @KerrekSB ilustraron por primera vez estos dos principios en sus respuestas. Esto es simplemente un intento de resaltar los dos problemas en una sola respuesta, con una redacción que apunta al problema en lugar de centrarse en el ejemplo.


Las tres respuestas existentes dan ejemplos en los que el uso de auto ayudas auto "hace que sea menos probable que se pesen involuntariamente" efectivamente, lo que lo hace "mejorar el rendimiento".

Hay un reverso de la moneda. Usar auto con objetos que tienen operadores que no devuelven el objeto básico puede dar como resultado un código incorrecto (aún compilable y ejecutable). Por ejemplo, esta pregunta pregunta cómo usar auto dio resultados diferentes (incorrectos) usando la biblioteca Eigen, es decir , las siguientes líneas

const auto resAuto = Ha + Vector3(0.,0.,j * 2.567); const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567); std::cout << "resAuto = " << resAuto <<std::endl; std::cout << "resVector3 = " << resVector3 <<std::endl;

resultó en una salida diferente. Es cierto que esto se debe principalmente a la evaluación perezosa de Eigens, pero ese código es / debería ser transparente para el usuario (de la biblioteca).

Si bien el rendimiento no se ha visto muy afectado aquí, el uso de auto para evitar la pesimismo involuntaria podría clasificarse como optimización prematura, o al menos incorrecta;).


auto puede ayudar al rendimiento al evitar conversiones implícitas silenciosas . Un ejemplo que encuentro convincente es el siguiente.

std::map<Key, Val> m; // ... for (std::pair<Key, Val> const& item : m) { // do stuff }

¿Ves el error? Aquí estamos, pensando que estamos tomando elegantemente cada elemento del mapa por referencia constante y usando la nueva expresión de rango para aclarar nuestra intención, pero en realidad estamos copiando cada elemento. Esto se debe a que std::map<Key, Val>::value_type es std::pair<const Key, Val> , no std::pair<Key, Val> . Por lo tanto, cuando (implícitamente) tenemos:

std::pair<Key, Val> const& item = *iter;

En lugar de tomar una referencia a un objeto existente y dejarlo así, tenemos que hacer una conversión de tipo. Se le permite tomar una referencia constante a un objeto (o temporal) de un tipo diferente siempre que haya una conversión implícita disponible, por ejemplo:

int const& i = 2.0; // perfectly OK

La conversión de tipo es una conversión implícita permitida por la misma razón por la que puede convertir una const Key en una Key , pero tenemos que construir una temporal del nuevo tipo para permitir eso. Por lo tanto, efectivamente nuestro bucle hace:

std::pair<Key, Val> __tmp = *iter; // construct a temporary of the correct type std::pair<Key, Val> const& item = __tmp; // then, take a reference to it

(Por supuesto, en realidad no hay un objeto __tmp , solo está ahí para ilustración, en realidad el temporal sin nombre solo está vinculado al item durante toda su vida útil).

Solo cambiando a:

for (auto const& item : m) { // do stuff }

simplemente nos ahorró una tonelada de copias; ahora el tipo de referencia coincide con el tipo de inicializador, por lo que no es necesario realizar una conversión temporal, solo podemos hacer una referencia directa.