guia - ¿Cuánto es demasiado con la palabra clave auto C++ 11?
qgis español (14)
Creo que uno debería usar la palabra clave auto
cuando sea difícil decir cómo escribir el tipo a primera vista, pero el tipo del lado derecho de una expresión es obvio. Por ejemplo, usando:
my_multi_type::nth_index<2>::type::key_type::composite_key_type::/
key_extractor_tuple::tail_type::head_type::result_type
para obtener el tipo de clave compuesta en boost::multi_index
, aunque sepa que es int
. No se puede escribir int
porque podría cambiarse en el futuro. Yo escribiría auto
en este caso.
Entonces, si la palabra clave auto
mejora la legibilidad en un caso particular, entonces utilícela. Puede escribir auto
cuando es obvio para el lector qué tipo de auto
representa.
Aquí hay unos ejemplos:
auto foo = std::make_shared<Foo>(); // obvious
auto foo = bla(); // unclear. don''t know which type `foo` has
const size_t max_size = 100;
for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors
// since max_size is unsigned
std::vector<some_class> v;
for ( auto it = v.begin(); it != v.end(); ++it ) // ok, since I know
// that `it` has an iterator type (don''t really care which one in this context)
He estado usando la nueva palabra clave auto
disponible en el estándar C ++ 11 para tipos de plantilla complicados, para lo cual creo que fue diseñada. Pero también lo estoy usando para cosas como:
auto foo = std::make_shared<Foo>();
Y más escépticamente por:
auto foo = bla(); // where bla() return a shared_ptr<Foo>
No he visto mucha discusión sobre este tema. Parece que el auto
podría ser usado en exceso, ya que un tipo es a menudo una forma de documentación y controles de validez. ¿Dónde dibuja la línea en auto
y cuáles son los casos de uso recomendados para esta nueva función?
Para aclarar: no estoy pidiendo una opinión filosófica; Solicito el uso previsto de esta palabra clave por parte del comité estándar, posiblemente con comentarios sobre cómo se realiza ese uso previsto en la práctica.
Nota al margen: Esta pregunta se movió a los programadores de SE. y luego de nuevo a Desbordamiento de pila. La discusión sobre esto se puede encontrar en esta meta pregunta .
En C ++ y más allá de 2012 en el panel Pregúntanos todo , hubo un intercambio fantástico entre Andrei Alexandrescu, Scott Meyers y Herb Sutter que hablaban sobre cuándo usar y no usar auto
. Salta al minuto 25:03 para una discusión de 4 minutos. Los tres altavoces dan excelentes puntos que deben tenerse en cuenta para cuándo no usar el auto
.
Animo encarecidamente a las personas a llegar a su propia conclusión, pero mi retiro fue usar el auto
todas partes a menos que :
- Duele la legibilidad
- Existe preocupación por la conversión automática de tipos (por ejemplo, de constructores, asignación, tipos intermedios de plantilla, conversión implícita entre anchos de enteros)
El uso liberal de lo explicit
ayuda a reducir la preocupación por lo último, lo que ayuda a minimizar la cantidad de tiempo que el primero es un problema.
Replanteando lo que Herb dijo, "si no estás haciendo X, Y y Z, usa auto
. Aprende qué son X, Y y Z y sigue adelante y usa auto
cualquier otro lugar".
Fácil. Úsalo cuando no te importe cuál es el tipo. Por ejemplo
for (const auto & i : some_container) {
...
Todo lo que me importa aquí es que i
sea 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 flotadores o dobles, solo que son del tipo que sea adecuado para expresar alturas y pesos .
O considerar
for (auto i = some_container .begin (); ...
Aquí todo lo que me importa es que es un iterador adecuado, que soporta operator++()
, es algo así como escribir pato a este respecto.
Además, el tipo de lambdas no se puede escribir, así que auto f = []...
es un buen estilo. La alternativa es lanzar 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 cierta redundancia en su código sin introducir efectos secundarios, debe ser bueno hacerlo.
Contraejemplos (tomados de las respuestas de otra persona):
auto i = SomeClass();
for (auto x = make_unsigned (y); ...)
Aquí nos importa el tipo, por lo que deberíamos escribir Someclass i;
y for(unsigned x = y;...
Sí, se puede utilizar en exceso en detrimento de la legibilidad. Sugiero usarlo en los contextos donde los tipos exactos son largos, inutilizables, o no importantes para la legibilidad, y las variables son de corta duración. Por ejemplo, el tipo de iterador generalmente es largo y no es importante, por lo que el auto
funcionaría:
for(auto i = container.begin(); i != container.end(); ++i);
Aquí el auto
no daña la legibilidad.
Otro ejemplo es el tipo de regla del analizador, que puede ser largo y complicado. Comparar:
auto spaces = space & space & space;
con
r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&> spaces =
space & space & space;
Por otro lado, cuando el tipo es conocido y es simple, es mucho mejor si se establece explícitamente:
int i = foo();
más bien que
auto i = foo();
Un peligro que he notado es en términos de referencias. p.ej
MyBigObject& ref_to_big_object= big_object;
auto another_ref = ref_to_big_object; // ?
El problema es que another_ref no es realmente una referencia en este caso es MyBigObject en lugar de MyBigObject &. Terminas copiando un objeto grande sin darte cuenta.
Si obtiene una referencia directamente de un método, es posible que no piense en lo que realmente es.
auto another_ref = function_returning_ref_to_big_object();
necesitarías "auto &" o "const auto &"
MyBigObject& ref_to_big_object= big_object;
auto& another_ref = ref_to_big_object;
const auto& yet_another_ref = function_returning_ref_to_big_object();
Una de mis experiencias amargas con auto
es usarlo con expresiones lambda :
auto i = []() { return 0; };
cout<<"i = "<<i<<endl; // output: 1 !!!
En realidad, aquí se resuelve el puntero de función de int(*)()
. Esto es solo un cout
simple, pero imagínese qué tipo de errores de compilación / tiempo de ejecución incorrectos puede causar cuando se usa con la template
.
Debería evitar el auto
con tales expresiones y poner un tipo de return
adecuado (o tipo de control controlado decltype()
)
El uso correcto para el ejemplo anterior sería,
auto i = []() { return 0; }(); // and now i contains the result of calling the lambda
Uno de los principales problemas con el programa C ++ es que le permite usar la variable sin inicializar . Esto nos lleva al comportamiento desagradable del programa no determinista. Se debe tener en cuenta que el compilador moderno ahora envía mensajes de advertencia apropiados / mensajes si el programa se cansa de usarlo.
Solo para ilustrar esto, considere el siguiente programa c ++:
int main() {
int x;
int y = 0;
y += x;
}
Si compilo este programa usando el compilador moderno (GCC), da la advertencia. Tal advertencia puede no ser muy obvia si estamos trabajando con el código de producción complejo real.
main.cpp: En la función ''int main ()'':
main.cpp: 4: 8: advertencia : ''x'' se usa sin inicializar en esta función [-Wuninitialized]
y + = x;
^
================================================== =============================== Ahora, si cambiamos nuestro programa que usa auto , luego compilamos obtenemos lo siguiente:
int main() {
auto x;
auto y = 0;
y += x;
}
main.cpp: En la función ''int main ()'':
main.cpp: 2: 10: error : la declaración de ''auto x'' no tiene inicializador
auto x; ^
Con auto, no es posible usar la variable sin inicializar. Esta es la principal ventaja que podemos obtener (gratis), si empezamos a utilizar el modo automático .
Herb Shutter, experto en C ++, explica este concepto y otro gran concepto moderno de C ++ en su charla sobre CppCon14 :
¡De vuelta a lo fundamental! Elementos esenciales del estilo moderno de C ++
Use auto
donde tenga sentido para inferir un tipo. Si tiene algo que sabe que es un número entero, o sabe que es una cadena, simplemente use int / std :: string, etc. No me preocuparía "abusar" de una función de idioma a menos que llegue al punto de ridiculez, u ofusca el código.
Esa es mi opinión de todos modos.
Use el auto
todos los lugares que pueda, particularmente el modo const auto
modo que los efectos secundarios sean menos preocupantes. No tendrá que preocuparse por los tipos, excepto en los casos obvios, pero aún así serán verificados estáticamente para usted, y puede evitar algunas repeticiones. Cuando el auto
no es factible, puede usar decltype
para expresar tipos semánticamente como contratos basados en expresiones. Su código se verá diferente, pero será un cambio positivo.
Uso auto
sin restricción y no tuve ningún problema. Incluso a veces termino usándolo para tipos simples como int
. Esto hace que c ++ sea un lenguaje de nivel superior para mí, y permite declarar variables en c ++ como en python. Después de escribir el código de Python, incluso a veces escribo por ejemplo
auto i = MyClass();
en lugar de
MyClass i;
Este es un caso donde diría que es un abuso de la palabra clave auto
.
A menudo no me importa cuál es el tipo exacto del objeto, me interesa más su carácter funcional y, como los nombres de las funciones generalmente dicen algo acerca de los objetos que devuelven, el auto
no duele: en, por ejemplo, auto s = mycollection.size()
, Puedo adivinar que s
será un tipo de entero, y en el raro caso de que me importe el tipo exacto, entonces verifiquemos la función del prototipo (quiero decir, prefiero tener que verificar cuando necesito la información, en lugar de a priori cuando se escribe el código, por si acaso sería útil algún día, como en int_type s = mycollection.size()
).
Respecto a este ejemplo de la respuesta aceptada:
for ( auto x = max_size; x > 0; --x )
En mi código, todavía uso auto
en este caso, y si quiero que x
no esté firmado, entonces uso una función de utilidad, llamada say make_unsigned
, que expresa claramente mis preocupaciones:
for ( auto x = make_unsigned(max_size); x > 0; --x )
descargo de responsabilidad: acabo de describir mi uso, no soy competente para dar consejos!
Ve a por ello. Utiliza el auto
cualquier lugar, lo que facilita la escritura de código.
Cada nueva característica en cualquier idioma será utilizada en exceso por al menos algunos tipos de programadores. Es solo a través de un uso excesivo moderado por parte de algunos programadores experimentados (no noobs) que el resto de los programadores experimentados aprenden los límites del uso adecuado. El uso excesivo extremo suele ser malo, pero podría ser bueno porque dicho uso excesivo puede llevar a mejoras en la función o una mejor función para reemplazarla.
Pero si estuviera trabajando en código con más de unas pocas líneas como
auto foo = bla();
donde el tipo se indica cero veces, es posible que desee cambiar esas líneas para incluir tipos. El primer ejemplo es excelente ya que el tipo se indica una vez, y el ahorro auto
nos obliga a tener que escribir tipos de plantilla desordenados dos veces. Hurra por C ++++. Pero mostrar explícitamente el tipo cero veces, si no es fácilmente visible en una línea cercana, me pone nervioso, al menos en C ++ y sus sucesores inmediatos. Para otros lenguajes diseñados para trabajar en un nivel superior con más abstracción, polimorfismo y genérico, está bien.
auto
palabra clave auto
solo se puede usar para variables locales, no para argumentos o miembros de clase / estructura. Por lo tanto, es seguro y viable utilizarlos en cualquier lugar que desee. Yo los uso mucho. El tipo se deduce en el momento de la compilación, el depurador muestra el tipo mientras realiza la depuración, el sizeof
informa correctamente, el tipo de decltype
daría el tipo correcto, no hay daño. ¡No cuento el auto
como usado en exceso, nunca!
auto
puede ser muy peligroso en combinación con las plantillas de expresión que se utilizan mucho en las bibliotecas de álgebra lineal, como Eigen o OpenCV.
auto A = Matrix(...);
auto B = Matrix(...);
auto C = A * B; // C is not a matrix. It is a matrix EXPRESSION.
cout << C; // The expression is evaluated and gives the expected result.
... // <code modifying A or B>
cout << C; // The expression is evaluated AGAIN and gives a DIFFERENT result.
Los errores causados por este tipo de errores son un gran problema para depurar. Una posible solución es emitir explícitamente el resultado al tipo esperado si está empeñado en utilizar el modo automático para el estilo de declaración de izquierda a derecha.
auto C = Matrix(A * B); // The expression is now evaluated immediately.
TL; DR: Ver la regla de oro en la parte inferior.
La respuesta aceptada sugiere la siguiente regla de oro:
Use
auto
cuando sea difícil decir cómo escribir el tipo a primera vista, pero el tipo del lado derecho de una expresión es obvio.
Pero diría que eso es demasiado restrictivo. En algún momento no me importan los tipos, ya que la declaración es lo suficientemente informativa sin que me moleste en tomarme el tiempo para averiguar el tipo. ¿Qué quiero decir con eso? Considere el ejemplo que ha aparecido en algunas de las respuestas:
auto x = f();
¿Qué hace esto un ejemplo de mal uso de auto
? ¿Es mi ignorancia del tipo de retorno de f()
? Bueno, de hecho podría ayudar si lo supiera, pero esa no es mi principal preocupación. Lo que es mucho más problemático es que x
y f()
carecen de sentido. Si tuvieramos:
auto nugget = mine_gold();
en cambio, generalmente no me importa si el tipo de retorno de la función es obvio o no. Al leer el enunciado, sé lo que estoy haciendo y sé lo suficiente sobre la semántica del valor de retorno para no sentir que necesito saber también su tipo.
Así que mi respuesta es: use auto
siempre que el compilador lo permita, a menos que:
- Siente que el nombre de la variable junto con la expresión de inicialización / asignación no proporciona suficiente información sobre lo que está haciendo la declaración.
- Siente que el nombre de la variable junto con la expresión de inicialización / asignación proporciona información "engañosa" sobre cuál debería ser el tipo, es decir, si tuviera que adivinar lo que viene en lugar del auto, podría hacer una conjetura, y sería mal, y esta suposición falsa tiene repercusiones más adelante en el código.
- Desea forzar un tipo diferente (por ejemplo, una referencia).
Y también:
- Prefiere dar un nombre significativo (que no contenga el nombre de tipo, por supuesto) antes de reemplazar el
auto
con el tipo concreto.