¿Cuánto es demasiado con la palabra clave automática C ++ 11?


218

He estado usando la nueva autopalabra clave disponible en el estándar C ++ 11 para tipos de plantillas 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 autopodría usarse en exceso, ya que un tipo es a menudo una forma de documentación y controles de sanidad. ¿Dónde trazas la línea de uso autoy cuáles son los casos de uso recomendados para esta nueva característica?

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 trasladó a SE.Programmers y luego a Stack Overflow. La discusión sobre esto se puede encontrar en esta meta pregunta .


Sin embargo, este es un sitio de preguntas y respuestas, no un sitio de discusión. Hiciste una pregunta muy general y dudo que alguien pueda darte algo más que una muy subjetiva. (por eso -1)
TravisG

15
@heishe, agregué una aclaración. Si lees la pregunta de manera muy general, parece estar pidiendo una opinión subjetiva, pero realmente si usaste la autopalabra clave, entonces sabes cómo se supone que debe usarse. Eso es lo que pregunto, como alguien que es nuevo en esta función, ¿cómo se supone que debo usarla?
Alan Turing

13
He visto esta discusión por todas partes cuando C # introdujo var(es decir, una vez que la gente superó la idea de que no era una escritura dinámica después de todo). Si lo desea, puede comenzar con esta pregunta y pasar por las preguntas relacionadas.
R. Martinho Fernandes

2
@Lex: O algo es legal o no lo es; llamar a algo "malo" que es legal es subjetivo por definición. Es decir, llamar auto foo = bla();"malo" es claramente una opinión, no un hecho, lo que hace esta pregunta y responde a una discusión, lo que la hace relevante para los Programadores SE, que es exactamente lo que indican los votos cerrados. / encogimiento de hombros
ildjarn

3
Opinión de Herb Sutter sobre este asunto: herbsutter.com/2013/06/13/…
rafak

Respuestas:


131

Creo que uno debe usar la autopalabra clave siempre que 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 boost::multi_index, aunque sepa que es así int. No puedes simplemente escribir intporque podría cambiarse en el futuro. Yo escribiría autoen este caso.

Entonces, si la autopalabra clave mejora la legibilidad en un caso particular, úsela. Puede escribir autocuando sea obvio para el lector qué tipo autorepresenta.

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)

18
Eso no parece una muy buena recomendación. ¿Con qué frecuencia no sabes el tipo de objeto? (Fuera de las plantillas, eso es). Y si no sabe que escriben, búsquelo, no sea flojo y use auto.
Paul Manta

99
@Paul: a menudo solo sabes o solo necesitas saber la parte más importante del tipo, por ejemplo, que es un iterador, pero no sabes y no te importa si es un iterador de vectores o un iterador de enlaces lista; en esos casos, realmente no desea gastar tiempo y espacio en la pantalla tratando de descubrir cómo escribir los tipos.
Lie Ryan

45
Ya sea que a los fanáticos de C ++ les guste o no, C ++ 0x atraerá a personas que nunca habrían usado C ++. Y esos usarán auto en todo el lugar.
Prof. Falken

66
@ R.MartinhoFernandes: no, lo único que está claro es que lo que sea que bla()regrese se lo estás dando foo.
Luis Machuca

8
@LuisMachuca, mi respuesta fue una manera irónica de decir que es deshonesto dar un ejemplo de libro de texto de mal nombre de variables y funciones y culpar su falta de legibilidad en la inferencia de tipos.
R. Martinho Fernandes

62

Úselo autodonde pueda, especialmente const autopara que los efectos secundarios sean menos preocupantes. No tendrá que preocuparse por los tipos, excepto en los casos obvios, pero todavía serán verificados estáticamente por usted y puede evitar algunas repeticiones. Donde autono sea factible, puede usarlo decltypepara expresar tipos semánticamente como contratos basados ​​en expresiones. Su código se verá diferente, pero será un cambio positivo.


12
Diría particularmente 'const auto &'
Viktor Sehr

2
Mejor uso auto&&en situaciones complejas edmundv.home.xs4all.nl/blog/2014/01/28/…
KindDragon

2
@KindDragon: Esa es una buena regla general, pero prefiero usar const auto&o, a const automenos que explícitamente, quiera mutar o mover.
Jon Purdy

" Usa el auto donde sea que puedas ". ¿Esto significa que debería escribir en auto str = std::string();lugar de std::string str;?
Calmarius

44
El uso automático de todo el lugar hará que su código sea menos legible y más difícil de depurar porque tendrá que deducir los tipos usted mismo mientras lee el código.
SubMachine

52

Fácil. Úselo cuando no le importa cuál es el tipo. Por ejemplo

for (const auto & i : some_container) {
   ...

Todo lo que me importa aquí es que isea ​​lo que haya en el contenedor.

Es un poco como typedefs.

typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;

En este caso, no me importa si hy wson flotadores o dobles, sólo que son cualquier tipo es 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, compatible operator++(), es como escribir un pato a este respecto.

Además, el tipo de lambdas no se puede deletrear, por lo que auto f = []...es un buen estilo. La alternativa es emitir std::functionpero eso viene con gastos generales.

Realmente no puedo concebir un "abuso" de auto. Lo más cercano que puedo imaginar es privarte de una conversión explícita a algún tipo significativo, pero no lo usarías autopara eso, construirías un objeto del tipo deseado.

Si puede eliminar algo de redundancia en su código sin introducir efectos secundarios, entonces debe ser bueno hacerlo.

Contraejemplos (tomados de las respuestas de otra persona):

auto i = SomeClass();
for (auto x = make_unsigned (y); ...)

Aquí nos importa cuál es el tipo, por lo que debemos escribir Someclass i;yfor(unsigned x = y;...


1
Um. No tan fácil. Se compila y se ejecuta, y acaba de dispararse en el pie si los elementos son objetos no triviales: su iteración llama al constructor y destructor de copias en cada paso de la iteración. Si va a usar ciegamente auto en un iterador basado en rango, probablemente debería ser "for (const auto & item: some_container)" en lugar de "for (auto item: some_container)".
Don Hatch

2
No siempre, pero está bien, probablemente quieras referencias. ¿Y qué? Eso no tiene nada que ver con eso auto.
pulverización

Realmente no entiendo tu último comentario. Traté de explicar por qué su estrategia no me parece muy buena y por qué la rechazo.
Don Hatch

55
Estoy diciendo que si usa referencias o no es ortogonal a si usa auto o no. Lo editaré para agregar la referencia porque es cierto que eso es lo que uno quiere hacer, pero es completamente irrelevante para el tema en cuestión.
rociar el

43

Ve a por ello. Úselo en autocualquier lugar que facilite 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 del uso excesivo moderado por algunos programadores experimentados (no novatos) 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 conducir 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 autonos ahorra tener que escribir dos tipos de plantillas desordenadas. ¡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.


38

En C ++ y más allá de 2012 en el panel Ask Us Anything , hubo un intercambio fantástico entre Andrei Alexandrescu, Scott Meyers y Herb Sutter hablando sobre cuándo usar y no usarauto . Pase al minuto 25:03 para una discusión de 4 minutos. Los tres altavoces ofrecen excelentes puntos que deben tenerse en cuenta para cuándo no usar auto.

Recomiendo encarecidamente a las personas que lleguen a su propia conclusión, pero mi objetivo fue usarlo en autotodas partes a menos que :

  1. Duele la legibilidad
  2. Existe preocupación acerca de la conversión automática de tipos (por ejemplo, de constructores, asignación, tipos intermedios de plantilla, conversión implícita entre anchos enteros)

El uso liberal de explicitayuda a reducir la preocupación por el segundo, lo que ayuda a minimizar la cantidad de tiempo que el primero es un problema.

Reformulando lo que dijo Herb, "si no estás haciendo X, Y y Z, usa auto. Aprende qué son X, Y y Z y ve y usa en autotodas partes".


44
Probablemente también valga la pena vincularlo a "casi siempre automático" - herbalutter.com/2013/08/12/…
Rob Starling

37

Sí, se puede usar en exceso en detrimento de la legibilidad. Sugiero usarlo en los contextos donde los tipos exactos son largos, indescifrables 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 autolo que funcionaría:

   for(auto i = container.begin(); i != container.end(); ++i);

auto aquí no hace daño 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 se conoce el tipo y es simple, es mucho mejor si declara explícitamente:

int i = foo();

más bien que

auto i = foo();

2
Por supuesto, tener un bucle for basado en rango en el lenguaje hace que su primer ejemplo sea menos emocionante. 8v)
Fred Larson

@Fred: el tipo aún puede ser engorroso (estoy pensando en contenedores asociativos)
Matthieu M.

3
@Fred: cada vez que tus límites no son begin()y end(), o el tamaño de tu paso es diferente a uno, o estás modificando el contenedor a medida que avanzas, la declaración basada en el rango no te ayudará.
Dennis Zickefoose

66
@geotavros: ¿Y lo r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&>hace?
Dennis Zickefoose

77
@geotavros: O puedes ver qué tipo spacees y buscarlo. Esa es la información más útil de todos modos ... después de todo, el problema no es "¿qué tipo es esta nueva variable" sino más bien "qué space & space & spacesignifica?" El tipo real de la expresión es solo ruido.
Dennis Zickefoose

19

auto puede ser muy peligroso en combinación con plantillas de expresión que se usan mucho en bibliotecas de álgebra lineal como Eigen u 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 dolor para depurar. Un posible remedio es emitir explícitamente el resultado al tipo esperado si está empeñado en usar auto para el estilo de declaración de izquierda a derecha.

auto C = Matrix(A * B); // The expression is now evaluated immediately.

1
Parece un comportamiento extraño para empezar. Si multiplico dos matrices, incluso si la operación es perezosa, no esperaría que fuera reevaluable, esperaría que mantuviera su estado evaluado después de la evaluación inicial. Si desea cambiar los parámetros sin modificar los parámetros originales, ¿no tendría que reconstruir la expresión de todos modos? ¿O está diseñado para un tipo de situación de procesamiento de transmisión, donde los parámetros originales cambian constantemente pero el procedimiento sigue siendo el mismo?
JAB

1
Ya sea que la A*Bexpresión se copie o no en una autovariable o en otra cosa, el comportamiento que usted describe sigue presente.
xtofl

Ningún error es causado por el uso auto.
Jim Balter

@JAB: está diseñado para admitir simplificaciones y sinergias entre operaciones, por ejemplo diag(A * B), no tiene que desperdiciar ciclos calculando los elementos fuera de la diagonal.
Ben Voigt

También he visto código como 'for (auto o: vecContainer)' donde 'o' era un objeto pesado que se copiaba cada vez. Mi recomendación sería usarlo para lo que se pretendía originalmente, es decir, plantillas (con pautas de deducción difíciles) y laboriosos tipos de definición. Incluso entonces debes ser cuidadoso y distinguir entre auto y auto &.
gast128

10

Utilizo autosin 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 vez de

MyClass i;

Este es un caso en el que diría que es un abuso de la autopalabra clave.

A menudo no me importa cuál es el tipo exacto del objeto, estoy más interesado en su funcionalidad, y como los nombres de las funciones generalmente dicen algo sobre los objetos que devuelven, autono duele: por ejemplo auto s = mycollection.size(), puedo suponer que sserá una especie de número entero, y en el raro caso en que me preocupe el tipo exacto, verifiquemos el prototipo de la función (quiero decir, prefiero tener que verificar cuando necesito la información, en lugar de a priori cuando se escribe el código, solo en caso de que sea útil algún día, como en int_type s = mycollection.size()).

Con respecto a este ejemplo de la respuesta aceptada:

for ( auto x = max_size; x > 0; --x )

En mi código todavía lo uso autoen este caso, y si quiero xestar sin firmar, 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!


1
@ChristianRau: no fue sarcástico. Tenga en cuenta que no recomendé el uso auto i = MyClass().
rafak

3
Seguimiento: ver: hierbasutter.com/2013/06/13/… . El uso de, por ejemplo, as_unsignedse recomienda allí, o incluso auto w = widget{};.
rafak

3

Uno de los principales problemas con el programa C ++ es que le permite usar la variable no inicializada . Esto nos lleva a un comportamiento desagradable del programa no determinista. Cabe señalar que el compilador moderno ahora arroja mensajes de advertencia apropiados / mensaje 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), me 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 , compilamos y 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 no inicializada. Esta es una gran ventaja que podemos obtener (gratis), si comenzamos a usar auto .

Este concepto y otro gran gran concepto moderno de C ++ es explicado por Herb Shutter, experto en C ++, en su charla CppCon14 :

¡De vuelta a lo basico! Elementos esenciales del estilo moderno de C ++


2
Si. Y para especificar el tipo, puede inicializar como 0i, 0u, 0l, 0ul, 0.0f, 0.0 o incluso int (), unsigned (), double (), etc.
Robinson

66
Este argumento es ridículo. Estás diciendo "no puedes olvidar el inicializador porque obtendrás un error de compilación", pero eso requiere que recuerdes usarlo auto.
Carreras de ligereza en órbita

1
@LightnessRacesinOrbit: me di cuenta (aprendí) este concepto de la charla de Herb Sutter. Encontré consejos lógicos / prácticos de la charla sobre hierbas, por lo tanto, pensé compartir con la comunidad.
Mantosh Kumar

2
No creo que esta sea una buena motivación para usar el auto. Simplemente active el indicador de advertencia de su compilador para usar variables no inicializadas y aborde todas las advertencias. Eso no quiere decir que no deba usar auto, pero no lo necesita para abordar este problema.
einpoklum

2

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 qué es realmente.

auto another_ref = function_returning_ref_to_big_object();

necesitaría "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();

1

Use autodonde tenga sentido que se infiera 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 por "usar en exceso" una característica del lenguaje a menos que llegue al punto de ridículo, u ofusca el código.

Esa es mi opinión de todos modos.


44
"donde tiene sentido ..." es la parte difícil. Los programadores pueden quedar tan atrapados en los detalles de la codificación, que pierden cualquier sentido de lo que tiene sentido para otros programadores, en particular para futuros mantenedores.
DarenW

Eso es cierto, aunque creo que es bastante fácil saber cuándo es ambiguo. En caso de duda, use un comentario!
LainIwakura

1
¿No tendría sentido usar un tipo?
Mikhail

¡Algunos desarrolladores dirían que el tipo siempre debe inferirse!
Arafangion

Use un comentario ...... o simplemente use el tipo y no necesita comentarios.
user997112

1

autoLa palabra clave solo se puede usar para variables locales, no para argumentos o miembros de clase / estructura. Por lo tanto, es seguro y viable usarlos en cualquier lugar que desee. Los uso mucho. El tipo se deduce en el momento de la compilación, el depurador muestra el tipo durante la depuración, lo sizeofinforma correctamente, decltypedaría el tipo correcto, no hay daño. ¡No cuento autocomo sobreutilizado, nunca!


0

TL; DR: Vea la regla general en la parte inferior.

La respuesta aceptada sugiere la siguiente regla general:

Úselo autosiempre que 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 yo diría que es demasiado restrictivo. A veces no me importan los tipos, ya que la declaración es lo suficientemente informativa sin que me moleste en tomarme el tiempo para resolver el tipo. ¿Qué quiero decir con eso? Considere el ejemplo que ha aparecido en algunas de las respuestas:

auto x = f();

¿Qué hace de esto un ejemplo de mal uso de auto? ¿Es mi ignorancia del f()tipo de retorno de? Bueno, de hecho puede ayudar si lo supiera, pero esa no es mi principal preocupación. Lo que es un problema mucho mayor es eso xy no f()tienen mucho 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 la declaración, 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.

Entonces mi respuesta es: usar autosiempre 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 adivinar, 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 contiene el nombre del tipo, por supuesto) antes de reemplazarlo autopor el tipo concreto.

-3

Una de mis amargas experiencias autoes usarlo con expresiones lambda :

auto i = []() { return 0; };
cout<<"i = "<<i<<endl; // output: 1 !!!

En realidad, aquí ise resuelve funcionar el puntero de int(*)(). Esto es simple cout, pero imagínese qué tipo de errores de compilación / tiempo de ejecución incorrectos puede causar cuando se usa template.

Debe evitar auto con tales expresiones y poner un returntipo apropiado (o 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  

77
Hiciste un trabajo terrible al explicar qué tiene que ver eso con el automóvil. Creaste una función y luego la imprimiste ... ¿De acuerdo?
Dennis Zickefoose

55
@iammilind: ¿y qué tiene eso que ver con auto?
R. Martinho Fernandes

77
Me parece altamente improbable que uno quiera ponerlo ()al final. Las lambdas están ahí para actuar como funciones y de ahí proviene la conversión del puntero de función. Si quieres llamarlo de inmediato, ¿por qué usar una lambda? auto i = 0;Funciona bastante bien.
R. Martinho Fernandes

44
¿Puede al menos describir un escenario donde auto x = []() { /* .... whatever goes in here ... */ }()es mejor que auto x = /* .... whatever goes in here ... */;, es decir, lo mismo, sin la lambda? Me parece bastante inútil, por la misma razón no auto x = 42 + y - 42tiene sentido.
R. Martinho Fernandes

66
-1 esto no es culpa del auto. El tipo de lambda no se puede deletrear, por lo que se requiere auto , si olvida llamar a la función, ¡ese es su propio puesto de observación! Podría poner un puntero de función no llamado en una C printf de la misma manera al azar.
pulverizar
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.