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.