Vi una conferencia de Herb Sutter donde anima a todos los programadores de C ++ a usar auto
.
Tuve que leer el código C # hace algún tiempo, donde var
se usaba ampliamente y el código era muy difícil de entender; cada vez que var
se usaba tenía que verificar el tipo de retorno del lado derecho. A veces más de una vez, ¡porque olvidé el tipo de variable después de un tiempo!
Sé que el compilador conoce el tipo y no tengo que escribirlo, pero es ampliamente aceptado que debemos escribir código para programadores, no para compiladores.
También sé que es más fácil de escribir:
auto x = GetX();
Que:
someWeirdTemplate<someOtherVeryLongNameType, ...>::someOtherLongType x = GetX();
Pero esto se escribe solo una vez y el GetX()
tipo de retorno se verifica muchas veces para comprender qué tipo x
tiene.
Esto me hizo preguntarme: ¿hace auto
que el código C ++ sea más difícil de entender?
auto
a menudo hace que las cosas sean más difíciles de leer cuando ya son difíciles de leer, es decir, funciones demasiado largas, variables mal nombradas, etc.auto
es muy parecido a determinar cuándo usartypedef
. Depende de usted determinar cuándo dificulta y cuándo ayuda.auto x = GetX();
, elija un nombre mejor que elx
que realmente le dice qué hace en ese contexto específico ... de todos modos, a menudo es más útil que su tipo.Respuestas:
Respuesta corta: Más completamente, mi opinión actual
auto
es que debe usarauto
por defecto a menos que desee explícitamente una conversión. (Un poco más precisamente, "... a menos que desee comprometerse explícitamente con un tipo, que casi siempre se debe a que desea una conversión").Respuesta más larga y justificación:
Escriba un tipo explícito (en lugar de
auto
) solo cuando realmente desee comprometerse explícitamente con un tipo, lo que casi siempre significa que desea obtener explícitamente una conversión a ese tipo. De la parte superior de mi cabeza, recuerdo dos casos principales:initializer_list
sorpresa queauto x = { 1 };
deduceinitializer_list
. Si no quiereinitializer_list
, diga el tipo, es decir, solicite explícitamente una conversión.auto x = matrix1 * matrix 2 + matrix3;
captura un tipo auxiliar o proxy que no debe ser visible para el programador. En muchos casos, está bien y es benigno capturar ese tipo, pero a veces si realmente desea que se colapse y haga el cálculo, diga el tipo, es decir, solicite nuevamente una conversión explícitamente.De manera rutinaria, use
auto
de manera predeterminada, de lo contrario, porque el usoauto
evita las trampas y hace que su código sea más correcto, más sostenible y robusto y más eficiente. Aproximadamente en orden de mayor a menor importancia, en el espíritu de "escribir para mayor claridad y corrección primero":auto
garantías obtendrá el tipo correcto. Como dice el refrán, si se repite (diga el tipo de forma redundante), puede mentir y mentirá (equivocarse). Aquí hay un ejemplo habitual:void f( const vector<int>& v ) { for( /*…*
en este punto, si escribe explícitamente el tipo de iterador, desea recordar escribirconst_iterator
(¿lo hizo?), Mientras queauto
lo hace bien.auto
hace que su código sea más robusto frente al cambio, porque cuando el tipo de expresión cambia,auto
continuará resolviéndose en el tipo correcto. Si, en cambio, se compromete con un tipo explícito, al cambiar el tipo de la expresión se inyectarán conversiones silenciosas cuando el nuevo tipo se convierta en el tipo anterior, o las compilaciones innecesarias se rompan cuando el nuevo tipo siga funcionando, como el tipo anterior pero no se convierta al antiguo tipo (por ejemplo, cuando cambia unmap
a ununordered_map
, lo cual siempre está bien si no confía en el orden, usandoauto
para sus iteradores cambiará sin problemas demap<>::iterator
aunordered_map<>::iterator
, pero usandomap<>::iterator
en todas partes significa explícitamente que estarás perdiendo tu valioso tiempo en una onda de corrección de código mecánico, a menos que un pasante esté caminando y puedas evitar el trabajo aburrido sobre ellos).auto
garantías ninguna conversión implícita sucederán, garantiza un mejor rendimiento por defecto. Si, por el contrario, dice el tipo y requiere una conversión, a menudo obtendrá una conversión silenciosa, lo haya esperado o no.auto
es su única buena opción para tipos difíciles de deletrear e indescifrables, como lambdas y ayudantes de plantilla, sin recurrir adecltype
expresiones repetitivas o indirecciones menos eficientesstd::function
.auto
es menos escribir. Menciono eso último para completar porque es una razón común para que te guste, pero no es la razón más importante para usarlo.Por lo tanto: Prefiero decir
auto
por defecto. Ofrece tanta simplicidad y rendimiento y bondad de claridad que solo te estás lastimando a ti mismo (y a los futuros mantenedores de tu código) si no lo haces. Solo comprométase con un tipo explícito cuando realmente lo diga, lo que casi siempre significa que desea una conversión explícita.Sí, hay (ahora) un GotW sobre esto.
fuente
auto x = static_cast<X>(y)
. Estostatic_cast
deja en claro que la conversión es a propósito y evita las advertencias del compilador sobre la conversión. Normalmente, evitar las advertencias del compilador no es tan bueno, pero estoy de acuerdo con no recibir una advertencia sobre una conversión que consideré cuidadosamente cuando escribístatic_cast
. Aunque no haría esto si no hay advertencias ahora, pero quiero recibir advertencias en el futuro si los tipos cambian de una manera potencialmente peligrosa.auto
es que debemos esforzarnos por programar contra interfaces (no en el sentido OOP), no contra implementaciones específicas. Es lo mismo con las plantillas, de verdad. ¿Se queja de "código difícil de leer" porque tiene un parámetro de tipo de plantillaT
que se usa en todas partes? No, no lo creo. En las plantillas también codificamos contra una interfaz, lo que mucha gente lo llama escribir en tiempo de compilación.auto
.auto
variable, pero casi todos lo hacen correctamente con una especificación de tipo explícita. Nadie usa IDE? ¿Todos usan solo variables int / float / bool? ¿Todos prefieren documentación externa para bibliotecas en lugar de encabezados auto documentados?=
RHS no tienen mucho sentido por ninguna otra interpretación (lista de inicio con barra, pero necesita saber lo que está inicializando, que es un oxímoronauto
). Lo que es sorprendenteauto i{1}
también es deducirinitializer_list
, a pesar de implicar que no tome esta lista de inicialización arriostrada, sino que tome esta expresión y use su tipo ... pero también llegamosinitializer_list
allí. Afortunadamente, C ++ 17 soluciona todo esto bien.Es una situación caso por caso.
A veces hace que el código sea más difícil de entender, a veces no. Tomemos, por ejemplo:
es definitivamente fácil de entender y definitivamente más fácil de escribir que la declaración real del iterador.
He estado usando C ++ por un tiempo ahora, pero puedo garantizar que obtendré un error de compilación en mi primer intento porque me olvidaría del
const_iterator
e inicialmente iría por eliterator
... :)Lo usaría para casos como este, pero no donde realmente ofusca el tipo (como su situación), pero esto es puramente subjetivo.
fuente
std::map<int, std::string>::const_iterator
, así que no es como si el nombre te dijera mucho sobre el tipo de todos modos.int
y el valor esstd::string
. :)it->second
ya que es un iterador constante. Todo lo cual la información es una repetición de lo que está en la línea anterior,const std::map<int, std::string>& x
. Decir cosas varias veces en ocasiones informa mejor, pero de ninguna manera es una regla general :-)for (anX : x)
hacerlo aún más obvio que solo estamos iterandox
. El caso normal donde necesita un iterador es cuando está modificando el contenedor, perox
esconst&
Míralo de otra manera. Escribes:
o:
A veces no ayuda deletrear el tipo explícitamente.
La decisión de si necesita mencionar el tipo no es la misma que la decisión de si desea dividir el código en varias declaraciones definiendo variables intermedias. En C ++ 03 los dos estaban vinculados, se puede considerar
auto
como una forma de separarlos.A veces, hacer explícitos los tipos puede ser útil:
vs.
En los casos en que declara una variable, el uso
auto
deja que el tipo no se exprese tal como lo es en muchas expresiones. Probablemente deberías intentar decidir por ti mismo cuándo eso ayuda a la legibilidad y cuándo dificulta.Puede argumentar que mezclar tipos con y sin signo es un error para empezar (de hecho, algunos argumentan además que no se deben usar tipos sin signo). La razón por la que es posiblemente un error es que hace que los tipos de operandos sean de vital importancia debido al comportamiento diferente. Si es malo saber los tipos de sus valores, entonces probablemente tampoco sea malo no tener que conocerlos. Entonces, siempre que el código no sea confuso por otras razones, eso lo hace
auto
correcto, ¿verdad? ;-)Particularmente al escribir código genérico, hay casos en los que el tipo real de una variable no debería ser importante, lo que importa es que satisfaga la interfaz requerida. Por lo tanto,
auto
proporciona un nivel de abstracción en el que ignora el tipo (pero, por supuesto, el compilador no lo hace, lo sabe). Trabajar en un nivel adecuado de abstracción puede ayudar bastante a la legibilidad, trabajar en el nivel "incorrecto" hace que leer el código sea un trabajo pesado.fuente
auto
permite crear variables con nombre con tipos sin nombre o sin interés. Los nombres significativos pueden ser útiles.sizeof
como unsigned en usted.OMI, estás viendo esto más o menos a la inversa.
No se trata de
auto
llevar a un código ilegible o incluso menos legible. Se trata de (tener la esperanza de que) tener un tipo explícito para el valor de retorno compensará el hecho de que (aparentemente) no está claro qué tipo sería devuelto por alguna función en particular.Al menos en mi opinión, si tiene una función cuyo tipo de retorno no es inmediatamente obvio, ese es su problema allí mismo. Lo que hace la función debería ser obvio por su nombre, y el tipo del valor de retorno debería ser obvio por lo que hace. Si no, esa es la verdadera fuente del problema.
Si hay un problema aquí, no es con
auto
. Es con el resto del código, y las posibilidades son bastante buenas de que el tipo explícito sea suficiente como una curita para evitar que vea y / o solucione el problema central. Una vez que haya solucionado ese problema real, la legibilidad del código usandoauto
generalmente estará bien.Supongo que, para ser justos, debería agregar: he tratado algunos casos en los que tales cosas no eran tan obvias como quisiera, y solucionar el problema también era bastante insostenible. Solo por un ejemplo, hice un poco de consultoría para una compañía hace un par de años que previamente se había fusionado con otra compañía. Terminaron con una base de código que estaba más "unida" que realmente fusionada. Los programas constitutivos habían comenzado a usar bibliotecas diferentes (pero bastante similares) para propósitos similares, y aunque estaban trabajando para fusionar las cosas de manera más limpia, aún lo hacían. En un buen número de casos, la única forma de adivinar qué tipo devolvería una función determinada era saber dónde se había originado esa función.
Incluso en ese caso, puede ayudar a aclarar algunas cosas. En ese caso, todo el código comenzó en el espacio de nombres global. El simple hecho de mover una cantidad considerable en algunos espacios de nombres eliminó los conflictos de nombres y facilitó bastante el seguimiento de tipos.
fuente
Hay varias razones por las que no me gusta el auto para uso general:
Pero espera, ¿es realmente una buena idea? ¿Qué pasa si el tipo importaba en media docena de esos casos de uso, y ahora ese código realmente se comporta de manera diferente? Esto también puede romper implícitamente la encapsulación, modificando no solo los valores de entrada, sino también el comportamiento de la implementación privada de otras clases que llaman a la función.
1a. Creo en el concepto de "código autodocumentado". El razonamiento detrás del código autodocumentado es que los comentarios tienden a estar desactualizados, ya no reflejan lo que está haciendo el código, mientras que el código en sí mismo, si se escribe de manera explícita, se explica por sí mismo, siempre se mantiene actualizado. en su intención, y no te dejará confundido con comentarios obsoletos. Sin embargo, si los tipos se pueden cambiar sin necesidad de modificar el código en sí, entonces el código / las variables en sí pueden volverse obsoletos. Por ejemplo:
auto bThreadOK = CheckThreadHealth ();
Excepto que el problema es que CheckThreadHealth () en algún momento fue refactorizado para devolver un valor de enumeración que indica el estado del error, si lo hay, en lugar de un bool. Pero la persona que realizó ese cambio no inspeccionó esta línea de código en particular, y el compilador no fue de ayuda porque compiló sin advertencias o errores.
Incluso funciona, probablemente. Digo que funciona, porque a pesar de que está haciendo una copia de una estructura de 500 bytes para cada iteración de bucle, por lo que puede inspeccionar un solo valor, el código sigue siendo completamente funcional. Entonces, incluso las pruebas de su unidad no lo ayudan a darse cuenta de que el código incorrecto se esconde detrás de ese auto simple e inocente. La mayoría de las personas que escanean el archivo tampoco lo notarán a primera vista.
Esto también puede empeorar si no sabe cuál es el tipo, pero elige un nombre de variable que hace una suposición errónea sobre lo que es, en realidad logra el mismo resultado que en 1a, pero desde el principio en lugar de post-refactor.
Me parece obvio que auto se introdujo principalmente como una solución alternativa para una sintaxis terrible con tipos de plantillas de biblioteca estándar. En lugar de tratar de corregir la sintaxis de la plantilla con la que las personas ya están familiarizadas, lo que también puede ser casi imposible de hacer debido a todo el código existente que podría romper, agregue una palabra clave que básicamente oculte el problema. Esencialmente, lo que podríamos llamar un "hack".
En realidad no tengo ningún desacuerdo con el uso de auto con contenedores de biblioteca estándar. Obviamente es para lo que se creó la palabra clave, y es probable que las funciones en la biblioteca estándar no cambien fundamentalmente su propósito (o tipo para el caso), lo que hace que el auto sea relativamente seguro de usar. Pero sería muy cauteloso al usarlo con su propio código e interfaces que pueden ser mucho más volátiles y potencialmente sujetos a cambios más fundamentales.
Otra aplicación útil de auto que mejora la capacidad del lenguaje es crear temporarios en macros independientes de tipo. Esto es algo que realmente no podías hacer antes, pero puedes hacerlo ahora.
fuente
auto something = std::make_shared<TypeWithLongName<SomeParam>>(a,b,c);
. :-)Sí, hace que sea más fácil saber el tipo de su variable si no la usa
auto
. La pregunta es: ¿ necesita saber el tipo de su variable para leer el código? A veces la respuesta será sí, a veces no. Por ejemplo, al obtener un iterador de astd::vector<int>
, ¿necesita saber que es unstd::vector<int>::iterator
o seríaauto iterator = ...;
suficiente? Todo lo que cualquiera quisiera hacer con un iterador está dado por el hecho de que es un iterador, simplemente no importa cuál sea el tipo específicamente.Úselo
auto
en esas situaciones cuando no haga que su código sea más difícil de leer.fuente
Personalmente lo uso
auto
solo cuando es absolutamente obvio para el programador lo que es.Ejemplo 1
Ejemplo 2
fuente
auto record = myObj.FindRecord(something)
, quedaría claro que el tipo de variable fue registro. O nombrarloit
o similar dejaría en claro que devuelve un iterador. Tenga en cuenta que, incluso si no lo usóauto
, nombrar correctamente la variable significaría que no necesita volver a la declaración para ver el tipo desde cualquier lugar de la función . Eliminé mi voto negativo porque el ejemplo no es un hombre de paja completo ahora, pero todavía no compro el argumento aquí.MyClass::RecordTy record = myObj.FindRecord (something)
Esta pregunta solicita opinión, que variará de un programador a otro, pero yo diría que no. De hecho, en muchos casos, todo lo contrario
auto
puede ayudar a que el código sea más fácil de entender al permitir que el programador se centre en la lógica en lugar de las minucias.Esto es especialmente cierto frente a los tipos de plantillas complejas. Aquí hay un ejemplo simplificado y artificial. ¿Cuál es más fácil de entender?
.. o ...
Algunos dirían que el segundo es más fácil de entender, otros pueden decir el primero. Sin embargo, otros podrían decir que un uso gratuito de
auto
puede contribuir a una tontería de los programadores que lo usan, pero esa es otra historia.fuente
std::map
ejemplos, además con argumentos de plantilla complejos.map
s. :)for
ejemplo, si los iteradores están invalidados en el cuerpo del bucle y, por lo tanto, deben pre-incrementarse o no incrementarse en absoluto.Muchas buenas respuestas hasta ahora, pero para centrarme en la pregunta original, creo que Herb va demasiado lejos en su consejo para usarlo
auto
libremente. Su ejemplo es un caso donde el usoauto
obviamente perjudica la legibilidad. Algunas personas insisten en que no es un problema con los IDE modernos donde puede pasar el mouse sobre una variable y ver el tipo, pero no estoy de acuerdo: incluso las personas que siempre usan un IDE a veces necesitan mirar fragmentos de código de forma aislada (piense en las revisiones de código , por ejemplo) y un IDE no ayudará.En pocas palabras: se usa
auto
cuando ayuda: es decir, iteradores en bucles for. No lo use cuando el lector tenga dificultades para averiguar el tipo.fuente
Estoy bastante sorprendido de que nadie haya señalado aún que el auto ayuda si no hay un tipo claro. En este caso, puede solucionar este problema utilizando un #define o typedef en una plantilla para encontrar el tipo utilizable real (y esto a veces no es trivial), o simplemente usa auto.
Supongamos que tiene una función, que devuelve algo con un tipo específico de plataforma:
¿Qué uso de brujas preferirías?
o simplemente
Claro que puedes escribir
también en alguna parte, pero
¿realmente contar algo más sobre el tipo de x? Le dice que es lo que se devuelve desde allí, pero es exactamente lo que es automático. Esto es redundante: 'cosas' se escriben 3 veces aquí, esto en mi opinión lo hace menos legible que la versión 'automática'.
fuente
typedef
ellos.La legibilidad es subjetiva; necesitará observar la situación y decidir qué es lo mejor.
Como señaló, sin auto, las declaraciones largas pueden producir mucho desorden. Pero como también señaló, las declaraciones breves pueden eliminar la información de tipo que puede ser valiosa.
Además de esto, también agregaría esto: asegúrese de que esté viendo la legibilidad y no la capacidad de escritura. El código que es fácil de escribir generalmente no es fácil de leer y viceversa. Por ejemplo, si estuviera escribiendo, preferiría auto. Si estuviera leyendo, tal vez las declaraciones más largas.
Entonces hay consistencia; ¿Qué tan importante es para ti? ¿Desea auto en algunas partes y declaraciones explícitas en otras, o un método consistente en todas partes?
fuente
Tomaré el punto de código menos legible como una ventaja, y alentaré al programador a usarlo cada vez más. ¿Por qué? Claramente, si el código que usa auto es difícil de leer, entonces también será difícil de escribir. El programador se ve obligado a utilizar el nombre de la variable significativa para mejorar su trabajo.
Quizás al principio el programador no escriba los nombres de las variables significativas. Pero eventualmente mientras corrige los errores, o en la revisión del código, cuando él / ella tiene que explicar el código a otros, o en un futuro no tan cercano, él / ella explica el código a las personas de mantenimiento, el programador se dará cuenta del error y usará El nombre de la variable significativa en el futuro.
fuente
myComplexDerivedType
para compensar el tipo que falta, lo que satura el código por la repetición de tipos (en todos los lugares donde se usa la variable), y que incita a las personas a omitir el propósito de la variable en su nombre . Mi experiencia es que no hay nada tan improductivo como poner obstáculos activamente en el código.Tengo dos pautas:
Si el tipo de la variable es obvio, tedioso de escribir o difícil de determinar, use auto.
Si necesita una conversión específica o el tipo de resultado no es obvio y puede causar confusión.
fuente
Si. Disminuye la verbosidad, pero el malentendido común es que la verbosidad disminuye la legibilidad. Esto solo es cierto si considera que la legibilidad es estética en lugar de su capacidad real de interpretar código, que no se incrementa mediante el uso de auto. En el ejemplo más comúnmente citado, los iteradores de vectores, puede parecer en la superficie que el uso de auto aumenta la legibilidad de su código. Por otro lado, no siempre sabes lo que te dará la palabra clave automática. Tienes que seguir la misma ruta lógica que el compilador para hacer esa reconstrucción interna, y muchas veces, en particular con iteradores, vas a hacer suposiciones incorrectas.
Al final del día, 'auto' sacrifica la legibilidad del código y la claridad, por la 'limpieza' sintáctica y estética (que solo es necesaria porque los iteradores tienen una sintaxis complicada innecesariamente) y la capacidad de escribir 10 caracteres menos en cualquier línea. No vale la pena el riesgo o el esfuerzo involucrado a largo plazo.
fuente