c++ - constint
¿Regla fácil de leer declaraciones complicadas de const? (2)
Para leer declaraciones de punteros complejos, existe la regla de derecha a izquierda .
Pero esta regla no menciona cómo leer los modificadores de const
.
Por ejemplo, en una declaración de puntero simple, const
se puede aplicar de varias maneras:
char *buffer; // non-const pointer to non-const memory
const char *buffer; // non-const pointer to const memory
char const *buffer; // equivalent to previous declartion
char * const buffer = {0}; // const pointer to non-const memory
char * buffer const = {0}; // error
const char * const buffer = {0}; // const pointer to const memory
Ahora, ¿qué pasa con el uso de const
con un puntero de declaración de puntero?
char **x; // no const;
const char **x;
char * const *x;
char * * const x;
const char * const * x;
const char * * const x;
const char * const * const x;
¿Y cuál es una regla fácil de leer esas declaraciones? ¿Qué declaraciones tienen sentido?
¿Es aplicable la regla de las agujas del reloj / espiral ?
Dos ejemplos del mundo real
El método ASTUnit::LoadFromCommandLine
usa const char **
para suministrar argumentos de línea de comando (en la fuente de clanv llvm).
El parámetro de vector argumento de getopt()
se declara así:
int getopt(int argc, char * const argv[], const char *optstring);
Donde char * const argv[]
es equivalente a char * const * argv
en ese contexto.
Dado que ambas funciones usan el mismo concepto (un vector de punteros a cadenas para suministrar los argumentos) y las declaraciones difieren, las preguntas obvias son: ¿por qué difieren? Tiene un sentido más que el otro?
La intención debería ser: El modificador const
debe especificar que la función no manipula las cadenas de este vector y no cambia la estructura del vector.
(Tratando de enfocarse en otros aspectos de la pregunta)
La regla de oro para las declaraciones de const es leerlas de derecha a izquierda y const
modifica el próximo token. Excepción: al comienzo de una declaración, const
modifica el token anterior.
Hay una razón de ser detrás de esta excepción : para las declaraciones elementales, const char c
busca a algunas personas más naturales que char const c
, y se informa que una forma precursora de const char c
es anterior a la regla const final.
getopt
int getopt(int argc, char * const argv[], const char *optstring);
o
int getopt(int argc, char * const * argv, const char *optstring);
Lo que significa que argv
es un puntero al vector const de punteros a cadenas no const.
Pero uno esperaría seguir la declaración:
int getopt(int argc, char const * const * argv, const char *optstring);
(puntero al vector const para const cadenas)
Debido a que getopt()
no se supone que cambie las cadenas a las que se hace referencia mediante argv.
Al menos char **
(como se usa en main()
) se convierte automáticamente en char * const * argv
.
Sonido metálico
ASTUnit::LoadFromCommandLine(..., const char **argv, ...);
Lo que significa que argv
es un puntero a un conjunto de punteros no const para const cadenas.
Nuevamente uno esperaría const char * const *argv
por la misma razón que arriba.
Pero esto es más notable porque char **
no se convierte en const char **
, por ejemplo
int main(int argc, char **argv) {
const char **x = argv; // Compile error!
return 0;
}
produce un error de compilación, donde
int main(int argc, char **argv) {
char * const *x = argv;
return 0;
}
y
int main(int argc, char **argv) {
const char * const *x = argv;
return 0;
}
no haga.
El modificador const
es trivial: modifica lo que le precede, a menos que nada lo preceda. Asi que:
char const* buffer; // const modifies char
char* const buffer; // const modifies *
, etc. En general, es mejor evitar las formas en las que nada precede a la const
, pero en la práctica, las vas a ver, así que debes recordar que cuando ningún tipo precede a la const
, tienes que moverla lógicamente detrás del primer tipo. Asi que:
const char** buffer;
es de hecho:
char const** buffer;
, es decir, puntero a puntero para const char.
Finalmente, en una declaración de función, a []
after lee como a *
before. (De nuevo, es probable que sea mejor evitar esta notación engañosa, pero la verás, así que debes lidiar con ella). Entonces:
char * const argv[], // As function argument
es:
char *const * argv,
un puntero a un puntero const a un char.