c++ - La nueva palabra clave "auto"; ¿Cuándo debería usarse para declarar un tipo de variable?
c++11 type-safety (6)
Úselo solo con tipos repetitivos largos, como plantillas largas y tipos de funciones lambda. Intenta evitarlo si puedes aclarar las cosas.
Posible duplicado:
¿Cuánto es demasiado con la palabra clave auto C ++ 0x
¿Tenemos (como comunidad) suficiente experiencia para determinar cuándo y / o si se está abusando del automóvil?
Lo que realmente estoy buscando es una guía de mejores prácticas en
- cuando usar auto
- cuando debe ser evitado
Reglas simples que pueden seguirse rápidamente en el 80% de los casos.
Como contexto, esta pregunta es provocada por mi respuesta here
Aplicaría la misma regla que para var
en C #: úsalo generosamente . Aumenta la legibilidad. A menos que el tipo de una variable sea realmente lo suficientemente importante como para establecerse explícitamente, en qué casos esto debería hacerse (duh).
Aún así, sostengo que (especialmente en lenguajes tipados estáticamente) el compilador es mucho mejor en rastrear tipos para nosotros que nosotros. La mayoría de las veces, el tipo exacto no es terriblemente importante de todos modos (de lo contrario, las interfaces no funcionarían en la práctica). Es más importante saber qué operaciones están permitidas. El contexto debería decirnos eso.
Además, la auto
puede evitar errores , evitando conversiones implícitas no deseadas en las inicializaciones. Generalmente, la declaración Foo x = y;
realizará una conversión implícita si y
no es de tipo Foo
y existe una conversión implícita. Esta es la razón para evitar tener conversiones implícitas en primer lugar. Desafortunadamente, C ++ ya tiene muchos de ellos.
Escribir auto x = y;
evitará este problema en principio .
Por otro lado, debe quedar claro que cuando realizo cálculos que asumen tal o cual número de bytes en un entero, el tipo explícito de la variable debe conocerse y debe indicarse claramente.
No todos los casos son tan claros, pero sostengo que la mayoría son, y eso
- en la mayoría de los casos es fácil ver si se necesita conocer un tipo explícito, y
- la necesidad de tipos explícitos es comparativamente rara.
Eric Lippert , desarrollador principal en el equipo compilador de C #, ha declarado lo mismo con respecto a var
.
Creo que cuando el tipo es muy conocido entre los co-programadores que trabajan (o trabajarían) en su proyecto, entonces se puede usar auto
, como en el siguiente código:
//good : auto increases readability here
for(auto it = v.begin(); it != v.end(); ++it) //v is some [std] container
{
//..
}
O, más en general,
//good : auto increases readability here
for(auto it = std::begin(v); it != std::end(v); ++it)//v could be array as well
{
//..
}
Pero cuando el tipo no es muy conocido y se utiliza con poca frecuencia, entonces creo que el auto
parece reducir la legibilidad, como aquí:
//bad : auto decreases readability here
auto obj = ProcessData(someVariables);
Mientras que en el primer caso, el uso del auto
parece muy bueno y no reduce la legibilidad, y por lo tanto, se puede usar ampliamente, pero en el último caso, reduce la legibilidad y, por lo tanto, no se debe usar.
Otro lugar donde se puede usar el auto
es cuando usa las new
funciones 1 o make_*
, como aquí:
//without auto. Not that good, looks cumbersome
SomeType<OtherType>::SomeOtherType * obj1 = new SomeType<OtherType>::SomeOtherType();
std::shared_ptr<XyzType> obj2 = std::make_shared<XyzType>(args...);
std::unique_ptr<XyzType> obj2 = std::make_unique<XyzType>(args...);
//With auto. good : auto increases readability here
auto obj1 = new SomeType<OtherType>::SomeOtherType();
auto obj2 = std::make_shared<XyzType>(args...);
auto obj3 = std::make_unique<XyzType>(args...);
Aquí es muy bueno, ya que reduce el uso del teclado, sin reducir la legibilidad, ya que cualquiera puede saber el tipo de objetos que se crean, simplemente mirando el código.
1. Sin embargo, evite el uso de punteros new
y crudos.
En algún momento, el tipo es tan irrelevante que el conocimiento del tipo ni siquiera es necesario, como en la plantilla de expresión ; de hecho, prácticamente es imposible escribir el tipo (correctamente), en tales casos el auto
es un alivio para los programadores. He escrito una biblioteca de plantillas de expresiones que se puede usar como:
foam::composition::expression<int> x;
auto s = x * x; //square
auto c = x * x * x; //cube
for(int i = 0; i < 5 ; i++ )
std::cout << s(i) << ", " << c(i) << std::endl;
Salida:
0, 0
1, 1
4, 8
9, 27
16, 64
Ahora compare el código anterior con el siguiente código equivalente que no usa auto
:
foam::composition::expression<int> x;
//scroll horizontally to see the complete type!!
foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply>> s = x * x; //square
foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply> >, foam::composition::expression<int>, foam::operators::multiply>> c = x * x * x; //cube
for(int i = 0; i < 5 ; i++ )
std::cout << s(i) << ", " << c(i) << std::endl;
Como puede ver, en tales casos, el auto
hace que su vida sea más fácil de manera exponencial. Las expresiones utilizadas anteriormente son muy simples; pensar en el tipo de expresiones más complejas:
auto a = x * x - 4 * x + 4;
auto b = x * (x + 10) / ( x * x+ 12 );
auto c = (x ^ 4 + x ^ 3 + x ^ 2 + x + 100 ) / ( x ^ 2 + 10 );
El tipo de tales expresiones sería aún más grande y feo, pero gracias a la auto
, ahora podemos permitir que el compilador infiera el tipo de expresiones.
La línea inferior es: la palabra clave auto
puede aumentar o disminuir la claridad y la legibilidad de su código, dependiendo del contexto . Si el contexto deja claro qué tipo es, o al menos cómo se debe usar (en el caso de un iterador de contenedor estándar) o si el conocimiento del tipo real ni siquiera es necesario (como en las plantillas de expresión), entonces debería ser auto
utilizado , y si el contexto no lo deja claro y no es muy común (como el segundo caso anterior), entonces debería evitarse .
Creo que la respuesta a tu primera pregunta es algo así como no. Sabemos lo suficiente como para elaborar algunas pautas sobre cuándo usar o evitar el auto
, pero todavía dejan bastantes casos en los que lo mejor que podemos decir actualmente es que todavía no podemos dar mucho en el camino de un asesoramiento objetivo sobre ellos.
El caso obvio en el que casi tiene que usarlo está en una plantilla cuando quiere (por ejemplo) el tipo adecuado para mantener el resultado de alguna operación en dos parámetros genéricos. En un caso como este, la única posibilidad de abuso no sería el abuso del auto
sí mismo, sino si el tipo general de operación que está haciendo (o el tipo de plantilla que está escribiendo, etc.) es algo que usted estar mejor evitando.
También hay al menos algunas situaciones en las que claramente debes evitar el auto
. Si está utilizando algo así como un tipo de proxy en el que depende de la conversión del proxy-> target para hacer parte del trabajo en cuestión, auto
(intentará) crear un objetivo del mismo tipo que el origen para que la conversión no sucederá En algunos casos, eso puede retrasar la conversión, pero en otros no funcionará en absoluto (por ejemplo, si el tipo de proxy no es compatible con la asignación, que a menudo es el caso).
Otro ejemplo sería cuando necesita asegurarse de que una variable en particular tenga un tipo específico por el bien de algo así como una interfaz externa. Solo por ejemplo, considere aplicar la máscara de red a una dirección IP (v4). En aras de la argumentación, supongamos que está trabajando con los octetos individuales de la dirección (por ejemplo, representando cada uno como un unsigned char
), por lo que terminamos con algo como octets[0] & mask[0]
. Gracias a las reglas de promoción tipo C, incluso si ambos operandos son unsigned char
, el resultado generalmente será int
. Sin embargo, necesitamos que el resultado sea un unsigned char
(es decir, un octeto), no un int
(normalmente 4 octetos). Como tal, en esta situación, el auto
sería casi seguro inapropiado.
Eso aún deja muchos casos en los que es una decisión de juicio. Mi propia tendencia para estos casos es tratar el auto
como el predeterminado, y solo usar un tipo explícito en los casos que son al menos un poco como el último caso que he citado anteriormente, incluso si un tipo particular no es necesario para corregir operación que realmente quiero un tipo particular, incluso si eso podría implicar una conversión implícita.
Mi suposición (pero es solo una suposición) es que con el tiempo, probablemente tenderé aún más en esa dirección. A medida que me vaya acostumbrando al compilador escogiendo los tipos, encontraré un buen número de casos en los que creo que debo especificar el tipo, realmente no necesito hacerlo y el código estará bien.
Sospecho que muchos de nosotros (y los mayores / más experimentados somos, probablemente mientras peor lo hagamos) utilizarán tipos explícitos por razones que en última instancia se remontan a cierto sentimiento sobre el rendimiento, y creen que nuestra elección mejorará el rendimiento . Parte del tiempo podemos tener razón, pero como la mayoría de nosotros con tanta experiencia hemos descubierto, nuestras suposiciones a menudo son erróneas (especialmente cuando se basan en suposiciones implícitas), y los compiladores y procesadores generalmente mejoran en tales cosas. con el tiempo también
Fácil. Úselo cuando no le importe el tipo. Por ejemplo
for (auto i : some_container) {
...
Todo lo que me importa aquí es que soy lo que sea que esté en el contenedor.
Es un poco como typedefs.
typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;
Aquí, no me importa si h
y w
son flotantes o dobles, solo que son del tipo que sea adecuado para expresar alturas y pesos .
O considera
for (auto i = some_container .begin (); ...
Aquí lo único que me importa es que es un iterador adecuado, que soporta el operator++()
, es como escribir pato al respecto.
Además, el tipo de lambdas no se puede escribir, por lo que auto f = []...
es un buen estilo. La alternativa es convertir a std::function
pero eso viene con gastos generales.
Realmente no puedo concebir un "abuso" de auto
. Lo más cercano que puedo imaginar es privarme de una conversión explícita a algún tipo significativo, pero no usaría auto
para eso, construiría un objeto del tipo deseado.
Si puede eliminar alguna redundancia en su código sin introducir efectos secundarios, entonces debe ser bueno hacerlo.
He usado idiomas con inferencia de tipo completo. No veo ninguna razón para no poner el auto
todos los auto
donde sea técnicamente posible *. De hecho, es posible que ya haya escrito auto i = 0;
, donde int
es un caracter mas corto que auto
. Ni siquiera estoy seguro de haberlo hecho, porque el fondo es: no me importa el tipado manifiesto.
*: por ejemplo, auto int[] = { 0, 1, 2, 3 }
no funciona.