Const antes o const después?

145

Para empezar, probablemente sepa que constpuede usarse para hacer que los datos de un objeto o un puntero no sean modificables o ambos.

const Object* obj; // can't change data
Object* const obj; // can't change pointer
const Object* const obj; // can't change data or pointer

Sin embargo, también puede usar la sintaxis:

Object const *obj; // same as const Object* obj;

Lo único que parece importar es qué lado del asterisco coloca la constpalabra clave. Personalmente, prefiero poner consta la izquierda del tipo para especificar que sus datos no se pueden modificar, ya que creo que se lee mejor en mi mentalidad de izquierda a derecha, pero ¿qué sintaxis vino primero?

Más importante aún, ¿por qué hay dos formas correctas de especificar constdatos y en qué situación preferiría o necesitaría una sobre la otra si la hubiera?

Editar:

Parece que esta fue una decisión arbitraria cuando el estándar de cómo los compiladores deberían interpretar las cosas fue redactado mucho antes de que yo naciera. Dado que constse aplica a lo que está a la izquierda de la palabra clave (¿de manera predeterminada?) Supongo que pensaron que no era perjudicial agregar "accesos directos" para aplicar palabras clave y calificadores de tipo de otras maneras, al menos hasta el momento en que la declaración cambie analizando un * o & ...

Este fue el caso también en C, entonces supongo.

AJG85
fuente
9
En las macros siempre agregue const después del tipo, por ejemplo, en #define MAKE_CONST(T) T constlugar de #define MAKE_CONST(T) const Tpara que MAKE_CONST(int *)se expanda correctamente en int * constlugar de const int *.
jotik
66
He visto estos dos estilos referidos como "const del este" y "const del oeste".
Tom Anderson
13
@TomAnderson pero realmente debería ser "const este" y "const oeste".
YSC

Respuestas:

96

¿Por qué hay dos formas correctas de especificar constdatos y en qué situación preferiría o necesitaría una sobre la otra si la hubiera?

Esencialmente, la razón por la cual la posición de los constespecificadores antes de un asterisco no importa es que Kernighan y Ritchie definieron la gramática C de esa manera.

La razón por la que definieron la gramática de esta manera era probable que su compilador de C analizara la entrada de izquierda a derecha y terminara de procesar cada token a medida que lo consumía. El consumo del *token cambia el estado de la declaración actual a un tipo de puntero. Encuentro constdespués *significa que el constcalificador se aplica a una declaración de puntero; encontrarlo antes de que el *calificador se aplique a los datos apuntados.

Debido a que el significado semántico no cambia si el constcalificador aparece antes o después de los especificadores de tipo, se acepta de cualquier manera.

Un tipo similar de caso surge al declarar punteros de función, donde:

  • void * function1(void)declara una función que devuelve void *,

  • void (* function2)(void)declara un puntero de función a una función que devuelve void.

Nuevamente, lo que hay que notar es que la sintaxis del lenguaje admite un analizador de izquierda a derecha.

Heath Hunnicutt
fuente
77
Kernighan fue coautor del libro pero no participó en el diseño de C, solo Ritchie.
Tom Zych
8
Nunca pude recordar cuál es cuál. Gracias a su explicación, finalmente tengo mnemotecnia para recordarlo. ¡Gracias! Antes de que el *analizador del compilador no sepa que es puntero, por lo tanto, es constante para el valor de los datos. Después * está relacionado con el puntero constante. Brillante. Y finalmente explica por qué puedo hacerlo const chartan bien como char const.
Vit Bernatik
2
La suposición de por qué se hizo de esta manera me parece bastante débil / contradictoria. Es decir, si estuviera definiendo un lenguaje y escribiendo un compilador, y quisiera mantenerlo simple y "analizar la entrada de izquierda a derecha y terminar de procesar cada token a medida que lo consume", como usted dice, me parece Requeriría constque siempre venga después de lo que está calificando ... exactamente para que siempre pueda terminar de procesar el constante inmediatamente después de consumirlo. Así que esto parece ser un argumento para prohibir West-Const, en lugar de permitirlo.
Don Hatch
3
"Debido a que el significado semántico no cambia si el calificador const aparece antes o después de los especificadores de tipo, se acepta de cualquier manera". ¿No es ese razonamiento circular? La pregunta es por qué el significado semántico se define así, así que no creo que esta oración contribuya en nada.
Don Hatch
1
@donhatch Debe recordar que, en relación con el presente y las suposiciones que hacemos en base a nuestra familiaridad con un buen diseño de lenguaje de programación, los lenguajes eran cosas bastante nuevas en aquel entonces. Además, si uno tiene un lenguaje permisivo o restringido es un juicio de valor. Por ejemplo, ¿debería Python tener un ++operador? "La oración", en mi humilde opinión, me ayudó a darme cuenta de que no había otra razón en particular que no fuera porque podían hacerlo. Tal vez harían una elección diferente hoy / tal vez no.
ragerdl
75

La regla es:

const se aplica a lo que queda de él. Si no hay nada a la izquierda, entonces se aplica a la cosa derecha.

Prefiero usar const a la derecha de la cosa para ser const solo porque es la forma "original" en que se define const.

Pero creo que este es un punto de vista muy subjetivo.

horstforst
fuente
18
Prefiero ponerlo a la izquierda, pero creo que ponerlo a la derecha tiene más sentido. Generalmente, lee tipos en C ++ de derecha a izquierda, por ejemplo, Object const *es un puntero a un objeto const. Si coloca el constde la izquierda, se leería como un puntero a un Objeto que es constante, que realmente no fluye muy bien.
Collin Dauphinee
1
Tengo la impresión de que a la izquierda se trata de una coherencia de estilo humano con otros tipos de declaraciones C (desde el punto de vista informático, no es correcto, ya constque no es una clase de almacenamiento, pero las personas no son analizadores).
geekosaur
1
@Heath Creo que es más una guía que una regla y lo he escuchado a menudo como una forma de recordar cómo lo interpretará el compilador ... Entiendo cómo funciona, así que solo tenía curiosidad sobre el proceso de pensamiento detrás de decisión de apoyarlo en ambos sentidos.
AJG85
3
@HeathHunnicutt la regla existe, pero es un poco más complicada: c-faq.com/decl/spiral.anderson.html
imallett
2
@HeathHunnicutt: la regla espiral es la versión expandida del comentario del primer comentarista "Generalmente lees tipos en [C /] C ++ de derecha a izquierda". Supuse que estabas contradiciendo esto. Sin embargo, creo que es posible que te hayas referido a la respuesta misma.
imallett
56

Prefiero la segunda sintaxis. Me ayuda a hacer un seguimiento de 'qué' es constante leyendo la declaración de tipo de derecha a izquierda:

Object * const obj;        // read right-to-left:  const pointer to Object
Object const * obj;        // read right-to-left:  pointer to const Object
Object const * const obj;  // read right-to-left:  const pointer to const Object
Matt Davis
fuente
3
Exactamente. Un "puntero constante a un objeto constante" Object const* constno lo es const const Object*. "const" no puede estar a la izquierda, excepto en el caso especial donde tanta gente lo adora. (Ver Heath arriba.)
cdunn2001
38

El orden de las palabras clave en una declaración no es tan fijo. Hay muchas alternativas para "el único orden verdadero". Me gusta esto

int long const long unsigned volatile i = 0;

o debería ser

volatile unsigned long long int const i = 0;

??

Bo Persson
fuente
25
+1 para una definición totalmente confusa de una variable simple. :)
Xeo
@rubenvb - Sí, desafortunadamente lo son. La gramática solo dice que a decl-specifier-seqes una secuencia de decl-specifiers. No hay un orden dado por la gramática, y el número de ocurrencias para cada palabra clave está limitado solo por algunas reglas semánticas (puede tener uno constpero dos long:-)
Bo Persson
3
@rubenvb: sí, unsignedes un tipo, igual que unsigned inty int unsigned. unsigned longes otro tipo, igual que unsigned long inty int long unsigned. ¿Ves el patrón?
Bo Persson
2
@Bo: Veo el desastre, tengo que tener tres para ver un patrón ;). OK, gracias
rubenvb
1
Solía ​​ser capaz de agregar statical revoltijo de palabras, pero solo recientemente los compiladores se quejaron de que staticdebe ser lo primero.
Mark Lakata
8

La primera regla es usar el formato que requieran sus estándares de codificación locales. Después de eso: poner el constfrente lleva a un sinfín de confusión cuando están involucrados typedefs, por ejemplo:

typedef int* IntPtr;
const IntPtr p1;   // same as int* const p1;

Si su estándar de codificación permite typedef's de punteros, entonces realmente debería insistir en poner la constante después del tipo. En todos los casos, pero cuando se aplica al tipo, const debe seguir a lo que se aplica, por lo que la coherencia también argumenta a favor del const posterior. Pero las pautas de codificación locales prevalecen sobre todas estas; La diferencia no suele ser lo suficientemente importante como para volver atrás y cambiar todo el código existente.

James Kanze
fuente
Creo que eso puede resaltar la razón por la cual no tenemos tipos de punteros en nuestros estándares más o menos definidos en esta tienda.
AJG85
1
Mi política (cuando puedo decidir solo) es poner la constante después (en aras de la coherencia) y no usar typedefs para punteros (o typedefs mucho en general) :-). Y por cierto, string :: iterator vs. string :: const_iterator probablemente también se debe tener en cuenta en su decisión. (Solo para confundir las cosas :-). No hay una respuesta correcta.)
James Kanze
Ah sí, también podría haber incluido el comportamiento de const std::string::const_iteratorbuena medida;)
AJG85
2
@JamesKanze - Espera un minuto, ayúdame aquí ... No veo la confusión en el ejemplo publicado. ¿Qué más podría const IntPtr p1significar que no sea "puntero entero constante" (es decir, "puntero constante a entero")? Nadie en su sano juicio, incluso sin saber cómo IntPtrse define, pensaría que p1es mutable. Y para el caso, ¿por qué alguien asumiría incorrectamente que *p1es inmutable? Además, poner la constante en cualquier otro lugar (p. Ej. IntPtr const p1) No cambia la semántica en absoluto.
Todd Lehman
3
@ToddLehman Puede que no vea la confusión, pero la mayoría de los programadores de C ++ sí, y sistemáticamente se equivocan (sin duda ayudados por cosas como std::vector<T>::const_iterator, donde no es el iterador el que es constante, sino a lo que apunta).
James Kanze
7

Hay razones históricas por las que se acepta izquierda o derecha. Stroustrup había agregado constante a C ++ en 1983 , pero no llegó a C hasta C89 / C90.

En C ++ hay una buena razón para usar siempre const a la derecha. Serás coherente en todas partes porque las funciones miembro const deben declararse de esta manera:

int getInt() const;
Nick Westgate
fuente
1
... bueno, la "buena razón" no es tan convincente, porque otras posibles ubicaciones para la "constante" no significarían lo mismo. const int& getInt(); int& const getInt();
Maestro
1
@Maestro: Estoy sugiriendo que int const& getInt();es mejor que el equivalente, const int& getInt();mientras int& const getInt();que compararlo es redundante (las referencias ya son constantes) aunque legales y generalmente darán una advertencia. De todos modos, const en una función miembro cambia el thispuntero en la función de Foo* consta Foo const* const.
Nick Westgate
consten una función miembro no significa en absoluto lo mismo, ¿o es void set(int)&;algún tipo de referencia a una función?
Davis Herring