¿Por qué declarar una variable en una línea y asignarla en la siguiente?

101

A menudo veo en el código C y C ++ la siguiente convención:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

en lugar de

some_type val = something;
some_type *ptr = &something_else;

Inicialmente supuse que este era un hábito que quedaba de los días en que tenía que declarar todas las variables locales en la parte superior del alcance. Pero he aprendido a no descartar tan rápidamente los hábitos de los desarrolladores veteranos. Entonces, ¿hay una buena razón para declarar en una línea y asignar después?

Jonathan Sterling
fuente
12
+1 para "He aprendido a no descartar tan rápidamente los hábitos de los desarrolladores veteranos". Esa es una sabia lección para aprender.
Comodín el

Respuestas:

92

C

En C89, todas las declaraciones tenían que estar al principio de un alcance ( { ... }), pero este requisito se eliminó rápidamente (primero con las extensiones del compilador y luego con el estándar).

C ++

Estos ejemplos no son lo mismo. some_type val = something;llama al constructor de la copia mientras val = something;llama al constructor predeterminado y luego a la operator=función. Esta diferencia es a menudo crítica.

Hábitos

Algunas personas prefieren declarar primero las variables y luego definirlas, en el caso de que vuelvan a formatear su código más tarde con las declaraciones en un lugar y la definición en otro.

Sobre los punteros, algunas personas simplemente tienen la costumbre de inicializar cada puntero NULLo nullptr, sin importar lo que hagan con ese puntero.

orlp
fuente
1
Gran distinción para C ++, gracias. ¿Qué pasa en la llanura C?
Jonathan Sterling
13
El hecho de que MSVC todavía no sea compatible con las declaraciones, excepto al comienzo de un bloque cuando se está compilando en modo C, es una fuente de irritación infinita para mí.
Michael Burr
55
@Michael Burr: Esto se debe a que MSVC no es compatible con C99.
orlp
3
"some_type val = something; llama al constructor de la copia": puede llamar al constructor de la copia, pero el estándar permite al compilador eludir la construcción por defecto de una construcción temporal, copia de val y la destrucción del val temporal y construir directamente usando un some_typeconstructor tomando somethingcomo único argumento. Este es un caso marginal muy interesante e inusual en C ++ ... significa que existe una presunción sobre el significado semántico de estas operaciones.
2
@Aerovistae: para los tipos incorporados son los mismos, pero no siempre se puede decir lo mismo para los tipos definidos por el usuario.
orlp
27

Ha etiquetado su pregunta C y C ++ al mismo tiempo, mientras que la respuesta es significativamente diferente en estos lenguajes.

En primer lugar, la redacción del título de su pregunta es incorrecta (o, más precisamente, irrelevante para la pregunta en sí). En ambos ejemplos, la variable se declara y define simultáneamente, en una línea. La diferencia entre sus ejemplos es que en el primero las variables se dejan sin inicializar o se inicializan con un valor ficticio y luego se le asigna un valor significativo más adelante. En el segundo ejemplo, las variables se inicializan de inmediato.

En segundo lugar, en lenguaje C ++, como señaló @nightcracker en su respuesta, estas dos construcciones son semánticamente diferentes. El primero se basa en la inicialización, mientras que el segundo, en la asignación. En C ++, estas operaciones son sobrecargables y, por lo tanto, pueden conducir a resultados diferentes (aunque se puede observar que producir sobrecargas no equivalentes de inicialización y asignación no es una buena idea).

En el lenguaje C estándar original (C89 / 90) es ilegal declarar variables en el medio del bloque, por lo que es posible que vea variables declaradas sin inicializar (o inicializadas con valores ficticios) al comienzo del bloque y luego asignadas significativas valores posteriores, cuando esos valores significativos estén disponibles.

En lenguaje C99 está bien declarar variables en el medio del bloque (al igual que en C ++), lo que significa que el primer enfoque solo es necesario en algunas situaciones específicas cuando el inicializador no se conoce en el punto de declaración. (Esto también se aplica a C ++).

Hormiga
fuente
2
@ Jonathan Sterling: leí tus ejemplos. Probablemente necesite repasar la terminología estándar de los lenguajes C y C ++. Específicamente, en los términos declaración y definición , que tienen significados específicos en estos idiomas. Lo repetiré nuevamente: en ambos ejemplos, las variables se declaran y definen en una línea. En C / C ++, la línea declara y definesome_type val; inmediatamente la variable . Esto es lo que quise decir en mi respuesta. val
1
Ya veo lo que quieres decir allí. Definitivamente tienes razón al declarar y definir que no tiene sentido la forma en que los usé. Espero que aceptes mis disculpas por la mala redacción y el comentario mal pensado.
Jonathan Sterling
1
Entonces, si el consenso es que "declarar" es la palabra incorrecta, sugeriría que alguien con un mejor conocimiento del estándar que yo edite la página Wikilibros.
Jonathan Sterling
2
En cualquier otro contexto, declarar sería la palabra correcta, pero dado que declarar es un concepto bien definido , con consecuencias, en C y C ++ no puede usarlo tan libremente como podría en otros contextos.
orlp
2
@ybungalobill: Estás equivocado. Declaración y definición en C / C ++ no son conceptos mutuamente excluyentes. En realidad, la definición es solo una forma específica de declaración . Cada definición es una declaración al mismo tiempo (con pocas excepciones). Hay declaraciones definitorias (es decir, definiciones) y declaraciones no definitorias. Además, normalmente la declaración térmica se usa todo el tiempo (incluso si es una definición), excepto en los contextos en los que la distinción entre ambos es crítica.
13

Creo que es un viejo hábito, sobrante de los tiempos de "declaración local". Y por lo tanto, como respuesta a su pregunta: No, no creo que haya una buena razón. Nunca lo hago yo mismo.


fuente
4

Dije algo sobre eso en mi respuesta a una pregunta de Helium3 .

Básicamente, digo que es una ayuda visual para ver fácilmente lo que ha cambiado.

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

y

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}
pmg
fuente
4

Las otras respuestas son bastante buenas. Hay algo de historia en torno a esto en C. En C ++ existe la diferencia entre un constructor y un operador de asignación.

Me sorprende que nadie mencione el punto adicional: mantener las declaraciones separadas del uso de una variable a veces puede ser mucho más legible.

Hablando visualmente, al leer el código, los artefactos más mundanos, como los tipos y nombres de variables, no son lo que te llama la atención. Son las declaraciones las que generalmente te interesan más, pasas la mayor parte del tiempo mirando y, por lo tanto, hay una tendencia a echar un vistazo al resto.

Si tengo algunos tipos, nombres y tareas, todo en el mismo espacio reducido, es un poco de sobrecarga de información. Además, significa que algo importante está sucediendo en el espacio que generalmente miro.

Puede parecer un poco contradictorio decirlo, pero esta es una instancia en la que hacer que su fuente ocupe más espacio vertical puede mejorarlo. Veo esto como una razón por la que no deberías escribir líneas repletas que hacen cantidades locas de aritmética y asignación de punteros en un espacio vertical apretado, solo porque el lenguaje te permite salirte con la tuya no significa que debas hacerlo todo el tiempo :-)

asveikau
fuente
2

En C, esta era la práctica estándar porque las variables debían declararse al comienzo de la función, a diferencia de C ++, donde podía declararse en cualquier parte del cuerpo de la función para ser utilizada posteriormente. Los punteros se establecieron en 0 o NULL, porque solo se aseguró de que el puntero no señalara basura. De lo contrario, no hay una ventaja significativa en la que pueda pensar, lo que obliga a cualquiera a hacer eso.

Vite Falcon
fuente
2

Pros para localizar definiciones de variables y su inicialización significativa:

  • Si a las variables se les asigna habitualmente un valor significativo cuando aparecen por primera vez en el código (otra perspectiva sobre lo mismo: retrasas su aparición hasta que un valor significativo esté disponible), entonces no hay posibilidad de que se usen accidentalmente con un valor sin sentido o sin inicializar ( lo que puede suceder fácilmente si se omite accidentalmente alguna inicialización debido a declaraciones condicionales, evaluación de cortocircuito, excepciones, etc.)

  • puede ser más eficiente

    • evita los gastos generales de establecer el valor inicial (construcción predeterminada o inicialización a algún valor centinela como NULL)
    • operator= a veces puede ser menos eficiente y requerir un objeto temporal
    • a veces (especialmente para funciones en línea) el optimizador puede eliminar algunas / todas las ineficiencias

  • minimizar el alcance de las variables a su vez minimiza el número promedio de variables simultáneamente en el alcance : esto

    • hace que sea más fácil hacer un seguimiento mentalmente las variables en su alcance, los flujos de ejecución y declaraciones que puedan afectar a dichas variables, y la importación de su valor
    • al menos para algunos objetos complejos y opacos, esto reduce el uso de recursos (montón, hilos, memoria compartida, descriptores) del programa
  • a veces más conciso ya que no estás repitiendo el nombre de la variable en una definición que en una asignación inicial significativa

  • necesario para ciertos tipos, como referencias y cuando desea que el objeto sea const

Argumentos para agrupar definiciones de variables:

  • a veces es conveniente y / o conciso factorizar el tipo de una serie de variables:

    the_same_type v1, v2, v3;

    (si la razón es solo que el nombre del tipo es demasiado largo o complejo, a typedefveces puede ser mejor)

  • a veces es deseable agrupar variables independientemente de su uso para enfatizar el conjunto de variables (y tipos) involucrados en alguna operación:

    type v1;
    type v2; type v3;

    Esto enfatiza la similitud del tipo y hace que sea un poco más fácil cambiarlos, sin dejar de seguir una variable por línea que facilita copiar, pegar, //comentar , etc.

Como suele ser el caso en la programación, si bien puede haber un beneficio empírico claro para una práctica en la mayoría de las situaciones, la otra práctica puede ser abrumadoramente mejor en algunos casos.

Tony
fuente
Desearía que más idiomas distinguieran el caso en el que el código declara y establece el valor de una variable que nunca se escribiría en otro lugar, aunque las nuevas variables podrían usar el mismo nombre [es decir, donde el comportamiento sería el mismo si las declaraciones posteriores usaran la misma variable o uno diferente], de aquellos en los que el código crea una variable que debe poder escribirse en varios lugares. Si bien ambos casos de uso se ejecutarán de la misma manera, saber cuándo las variables pueden cambiar es muy útil al tratar de rastrear errores.
supercat