ejemplo data c++ c++11 type-inference auto

data - auto c++ ejemplo



¿Hay una desventaja en declarar variables con auto en C++? (13)

Parece que auto fue una característica bastante importante que se agregó en C ++ 11 que parece seguir muchos de los lenguajes más nuevos. Al igual que con un lenguaje como Python, no he visto ninguna declaración explícita de variables (no estoy seguro de si es posible usar los estándares de Python).

¿Hay algún inconveniente en usar auto para declarar variables en lugar de declararlas explícitamente?


Como this desarrollador, odio el auto . O más bien, odio la forma en que la gente usa mal el auto .

Soy de la opinión (fuerte) de que auto es para ayudarlo a escribir código genérico, no para reducir la escritura .
C ++ es un lenguaje cuyo objetivo es permitirle escribir código robusto, no minimizar el tiempo de desarrollo.
Esto es bastante obvio a partir de muchas características de C ++, pero desafortunadamente algunas de las más nuevas, como la auto que reducen la escritura, inducen a las personas a pensar que deberían comenzar a ser perezosas.

En los días previos al auto , la gente usaba typedef s, lo cual fue genial porque typedef permitió que el diseñador de la biblioteca lo ayudara a determinar cuál debería ser el tipo de retorno, para que su biblioteca funcione como se esperaba. Cuando usas auto , le quitas ese control al diseñador de la clase y en su lugar le pides al compilador que descubra cuál debería ser el tipo, lo que elimina una de las herramientas C ++ más poderosas de la caja de herramientas y se arriesga a romper su código.

En general, si usa auto , debe ser porque su código funciona para cualquier tipo razonable , no porque sea demasiado vago para escribir el tipo con el que debería funcionar. Si usa auto como herramienta para ayudar a la pereza, entonces lo que sucede es que eventualmente comienza a introducir errores sutiles en su programa, generalmente causados ​​por conversiones implícitas que no ocurrieron porque usó auto .

Desafortunadamente, estos errores son difíciles de ilustrar en un breve ejemplo aquí porque su brevedad los hace menos convincentes que los ejemplos reales que surgen en un proyecto de usuario; sin embargo, ocurren fácilmente en un código pesado de plantilla que espera que se produzcan ciertas conversiones implícitas lugar.

Si quieres un ejemplo, hay uno here . Sin embargo, una pequeña nota: antes de sentirse tentado a saltar y criticar el código: tenga en cuenta que muchas bibliotecas conocidas y maduras se han desarrollado en torno a conversiones implícitas, y están allí porque resuelven problemas que pueden ser difíciles, si no imposibles. para resolver lo contrario. Intenta encontrar una mejor solución antes de criticarlos.


Como describí en esta respuesta, el auto veces puede dar lugar a situaciones funky que no pretendía. Debe decir explícitamente auto& tener un tipo de referencia mientras que solo auto puede crear un tipo de puntero. Esto puede generar confusión al omitir todo el especificador, lo que da como resultado una copia de la referencia en lugar de una referencia real.


Creo que auto es bueno cuando se usa en un contexto localizado, donde el lector puede deducir fácilmente su tipo, o bien documentado con un comentario de su tipo o un nombre que infiera el tipo real. Aquellos que no entienden cómo funciona pueden tomarlo de manera incorrecta, como usarlo en lugar de una template o similar. Aquí hay algunos casos de uso buenos y malos en mi opinión.

void test (const int & a) { // b is not const // b is not a reference auto b = a; // b type is decided by the compiler based on value of a // a is int }

Buenos usos

Iteradores

std::vector<boost::tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int> v(); .. std::vector<boost::tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int>::iterator it = v.begin(); // VS auto vi = v.begin();

Punteros de funciones

int test (ClassWithLongName1 a, ClassWithLongName2 b, int c) { .. } .. int (*fp)(ClassWithLongName1, ClassWithLongName2, int) = test; // VS auto *f = test;

Malos usos

Flujo de datos

auto input = ""; .. auto output = test(input);

Firma de función

auto test (auto a, auto b, auto c) { .. }

Casos triviales

for(auto i = 0; i < 100; i++) { .. }


Esto no es exactamente un inconveniente del auto de una manera basada en principios, pero en términos prácticos parece ser un problema para algunos. Básicamente, algunas personas: a) tratan el auto como un salvador para los tipos y apagan su cerebro cuando lo usan, o b) olvidan que el auto siempre deduce los tipos de valor. Esto hace que las personas hagan cosas como esta:

auto x = my_obj.method_that_returns_reference();

Vaya, solo copiamos en profundidad algún objeto. A menudo es un error o un fallo de rendimiento. Luego, también puedes girar hacia el otro lado:

const auto& stuff = *func_that_returns_unique_ptr();

Ahora obtienes una referencia colgante. Estos problemas no son causados ​​por el auto en absoluto, por lo que no los considero argumentos legítimos en su contra. Pero parece que el auto hace que este problema sea más común (desde mi experiencia personal), por las razones que mencioné al principio.

Creo que, con el tiempo, las personas se adaptarán y comprenderán la división del trabajo: auto deduce el tipo subyacente, pero aún así desea pensar en la referencia y la constancia. Pero está tomando un poco de tiempo.


Hace que su código sea un poco más difícil o tedioso de leer. Imagina algo así:

auto output = doSomethingWithData(variables);

Ahora, para determinar el tipo de salida, tendría que rastrear la firma de la función doSomethingWithData .


La palabra clave auto simplemente deduce el tipo del valor de retorno. Por lo tanto, no es equivalente a un objeto Python, p. Ej.

# Python a a = 10 # OK a = "10" # OK a = ClassA() # OK // C++ auto a; // Unable to deduce variable a auto a = 10; // OK a = "10"; // Value of const char* can''t be assigned to int a = ClassA{} // Value of ClassA can''t be assigned to int a = 10.0; // OK, implicit casting warning

Como auto se deduce durante la compilación, no tendrá ningún inconveniente en tiempo de ejecución.


Lo que nadie mencionó aquí hasta ahora, pero por sí mismo vale una respuesta si me lo preguntas.

Dado que (incluso si todos deben saber que C != C++ ) el código escrito en C puede diseñarse fácilmente para proporcionar una base para el código C ++ y, por lo tanto, puede diseñarse sin demasiado esfuerzo para ser compatible con C ++, esto podría ser un requisito para el diseño.

Conozco algunas reglas donde algunas construcciones bien definidas de C no son válidas para C++ y viceversa. Pero esto simplemente daría como resultado archivos ejecutables rotos y se aplica la conocida cláusula UB, que la mayoría de las veces se nota por bucles extraños que resultan en bloqueos o lo que sea (o incluso pueden pasar desapercibidos, pero eso no importa aquí).

¡Pero el auto es la primera vez que esto cambia!

Imagina que usaste auto como especificador de clase de almacenamiento antes y transfieres el código. Ni siquiera necesariamente (dependiendo de la forma en que se usó) se "rompería"; en realidad podría cambiar silenciosamente el comportamiento del programa.

Eso es algo que uno debe tener en cuenta.

1 Al menos la primera vez que tengo conocimiento.


Otras respuestas mencionan inconvenientes como "realmente no sabes cuál es el tipo de variable". Yo diría que esto está relacionado en gran medida con la convención de nomenclatura descuidada en el código. Si sus interfaces están claramente nombradas, no debería tener que preocuparse de cuál es el tipo exacto. Claro, auto result = callSomeFunction(a, b); no te dice mucho Pero auto valid = isValid(xmlFile, schema); le dice lo suficiente para usar valid sin tener que preocuparse de cuál es su tipo exacto. Después de todo, con solo if (callSomeFunction(a, b)) , tampoco conocería el tipo. Lo mismo con cualquier otro objeto temporal de subexpresión. Así que no considero que esto sea un verdadero inconveniente del auto .

Yo diría que su principal inconveniente es que a veces, el tipo de retorno exacto no es con lo que desea trabajar. En efecto, a veces el tipo de retorno real difiere del tipo de retorno "lógico" como un detalle de implementación / optimización. Las plantillas de expresión son un excelente ejemplo. Digamos que tenemos esto:

SomeType operator* (const Matrix &lhs, const Vector &rhs);

Lógicamente, esperaríamos que SomeType sea Vector , y definitivamente queremos tratarlo como tal en nuestro código. Sin embargo, es posible que para fines de optimización, la biblioteca de álgebra que estamos utilizando implemente plantillas de expresión, y el tipo de retorno real es este:

MultExpression<Matrix, Vector> operator* (const Matrix &lhs, const Vector &rhs);

Ahora, el problema es que MultExpression<Matrix, Vector> con toda probabilidad almacenará una const Matrix& y const Vector& internamente; espera que se convierta en un Vector antes del final de su expresión completa. Si tenemos este código, todo está bien:

extern Matrix a, b, c; extern Vector v; void compute() { Vector res = a * (b * (c * v)); // do something with res }

Sin embargo, si hubiéramos usado el auto aquí, podríamos tener problemas:

void compute() { auto res = a * (b * (c * v)); // Oops! Now `res` is referring to temporaries (such as (c * v)) which no longer exist }


Otro ejemplo irritante:

for (auto i = 0; i < s.size(); ++i)

genera una advertencia ( comparison between signed and unsigned integer expressions [-Wsign-compare] ), porque i es un int con signo. Para evitar esto, debe escribir, por ejemplo

for (auto i = 0U; i < s.size(); ++i)

o tal vez mejor:

for (auto i = 0ULL; i < s.size(); ++i)


Solo ha preguntado acerca de los inconvenientes, por lo que estoy destacando algunos de ellos. Cuando se usa bien, el auto tiene varias ventajas. Los inconvenientes resultan de la facilidad de abuso y del mayor potencial para que el código se comporte de manera no intencional.

El principal inconveniente es que, al usar auto , no necesariamente conoce el tipo de objeto que se está creando. También hay ocasiones en las que el programador puede esperar que el compilador deduzca un tipo, pero el compilador deduce firmemente otro.

Dada una declaración como

auto result = CallSomeFunction(x,y,z);

no necesariamente tiene conocimiento de qué tipo de result es. Puede ser un int . Puede ser un puntero. Puede ser otra cosa. Todos ellos admiten diferentes operaciones. También puede cambiar drásticamente el código mediante un cambio menor como

auto result = CallSomeFunction(a,y,z);

porque, dependiendo de las sobrecargas que existan para CallSomeFunction() el tipo de resultado puede ser completamente diferente y, por lo tanto, el código posterior puede comportarse de manera completamente diferente de lo previsto. Es posible que de repente se activen mensajes de error en el código posterior (por ejemplo, luego intente desreferenciar un int , intente cambiar algo que ahora es const ) El cambio más siniestro es donde su cambio pasa por el compilador, pero el código posterior se comporta de formas diferentes y desconocidas, posiblemente con errores.

Por lo tanto, no tener un conocimiento explícito del tipo de algunas variables hace que sea más difícil justificar rigurosamente una afirmación de que el código funciona según lo previsto. Esto significa un mayor esfuerzo para justificar las afirmaciones de "apto para el propósito" en dominios de alta criticidad (por ejemplo, críticos de seguridad o de misión crítica).

El otro inconveniente, más común, es la tentación de que un programador use auto como un instrumento contundente para forzar la compilación del código, en lugar de pensar en lo que está haciendo el código y trabajar para hacerlo bien.


Una de las razones por las que puedo pensar es que pierdes la oportunidad de obligar a la clase que se devuelve. Si su función o método devolvió un largo 64 bits, y solo quería un 32 sin signo int, entonces pierde la oportunidad de controlar eso.


Uno de los inconvenientes es que a veces no se puede declarar const_iterator con auto . Obtendrá un iterador ordinario (no constante) en este ejemplo de código tomado de esta pregunta :

map<string,int> usa; //...init usa auto city_it = usa.find("New York");


auto no tiene inconvenientes per se , y recomiendo usarlo (a mano) en todas partes en el nuevo código. Permite que su código compruebe constantemente el tipo y evite sistemáticamente el corte silencioso. (Si B deriva de A y una función que devuelve A repente devuelve B , entonces auto comporta como se espera para almacenar su valor de retorno)

Sin embargo, el código heredado anterior a C ++ 11 puede depender de conversiones implícitas inducidas por el uso de variables explícitamente tipadas. Cambiar una variable de tipo explícito a auto podría cambiar el comportamiento del código , por lo que será mejor que sea cauteloso.