¿El uso de const en los parámetros de función tiene algún efecto? ¿Por qué no afecta la firma de la función?
Por ejemplo, imagine un mutador simple que toma un único parámetro booleano:
void SetValue(const bool b) { my_val_ = b; }
¿Eso const
realmente tiene algún impacto? Personalmente opto por usarlo ampliamente, incluidos los parámetros, pero en este caso me pregunto si hace alguna diferencia.
También me sorprendió saber que puedes omitir const
parámetros en una declaración de función pero puedes incluirlos en la definición de función, por ejemplo:
archivo .h
void func(int n, long l);
archivo .cpp
void func(const int n, const long l) { /* ... */ }
¿Hay alguna razón para esto? Me parece un poco inusual.
const
no tiene sentido cuando el argumento se pasa por valor ya que no modificará el objeto de la persona que llama.
Equivocado.
Se trata de autodocumentar su código y sus suposiciones.
Si su código tiene muchas personas trabajando en él y sus funciones no son triviales, entonces debe marcar const
todo lo que pueda. Al escribir código industrial, siempre debes asumir que tus compañeros de trabajo son psicópatas que intentan atraparte de cualquier manera que puedan (especialmente porque a menudo eres tú mismo en el futuro).
Además, como alguien mencionó anteriormente, podría ayudar al compilador a optimizar un poco las cosas (aunque es una posibilidad remota).
La razón es que const
el parámetro solo se aplica localmente dentro de la función, ya que está trabajando en una copia de los datos. Esto significa que la firma de la función es realmente la misma de todos modos. Probablemente sea de mal estilo hacer esto mucho.
Personalmente tiendo a no usarlo const
excepto para parámetros de referencia y puntero. Para objetos copiados realmente no importa, aunque puede ser más seguro ya que indica intención dentro de la función. Es realmente una cuestión de criterio. Tiendo a usarlo const_iterator
cuando hago un bucle en algo y no tengo la intención de modificarlo, así que supongo que cada uno lo suyo, siempre y cuando const
se mantenga rigurosamente la corrección de los tipos de referencia.
A veces (¡demasiado a menudo!) tengo que desenredar el código C++ de otra persona. Y todos sabemos que el código C++ de otra persona es un completo desastre casi por definición :) Entonces, lo primero que hago para descifrar el flujo de datos local es poner const en cada definición de variable hasta que el compilador comience a ladrar. Esto también significa argumentos de valor de calificación constante, porque son simplemente variables locales sofisticadas inicializadas por la persona que llama.
Ah, desearía que las variables fueran constantes de forma predeterminada y se requiriera mutable para las variables no constantes :)
Las const extra superfluas son malas desde el punto de vista de la API:
Poner constantes adicionales superfluas en su código para parámetros de tipo intrínseco pasados por valor satura su API y no hace ninguna promesa significativa a la persona que llama o al usuario de API (solo obstaculiza la implementación).
Demasiadas 'const' en una API cuando no son necesarias es como " lobo llorando ", eventualmente la gente comenzará a ignorar 'const' porque está por todas partes y no significa nada la mayor parte del tiempo.
El argumento "reductio ad absurdum" para que las constantes adicionales en API sean buenas para estos dos primeros puntos sería que si más parámetros constantes son buenos, entonces cada argumento que pueda tener una constante DEBE tener una constante. De hecho, si fuera realmente tan bueno, querrás que const sea el valor predeterminado para los parámetros y que tenga una palabra clave como "mutable" solo cuando quieras cambiar el parámetro.
Así que intentemos poner const siempre que podamos:
void mungerum(char * buffer, const char * mask, int count);
void mungerum(char * const buffer, const char * const mask, const int count);
Considere la línea de código anterior. No sólo la declaración es más confusa, más larga y más difícil de leer, sino que el usuario de la API puede ignorar con seguridad tres de las cuatro palabras clave 'const'. Sin embargo, el uso adicional de 'const' ha hecho que la segunda línea sea potencialmente PELIGROSA.
¿Por qué?
Una rápida lectura errónea del primer parámetro char * const buffer
podría hacerle pensar que no modificará la memoria en el búfer de datos que se pasa; sin embargo, ¡esto no es cierto! Una "constancia" superflua puede llevar a suposiciones peligrosas e incorrectas sobre su API cuando se escanea o se lee mal rápidamente.
Las constantes superfluas también son malas desde el punto de vista de la implementación del código:
#if FLEXIBLE_IMPLEMENTATION
#define SUPERFLUOUS_CONST
#else
#define SUPERFLUOUS_CONST const
#endif
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count);
Si FLEXIBLE_IMPLEMENTATION no es cierto, entonces la API "promete" no implementar la función de la primera manera a continuación.
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count)
{
// Will break if !FLEXIBLE_IMPLEMENTATION
while(count--)
{
*dest++=*source++;
}
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count)
{
for(int i=0;i<count;i++)
{
dest[i]=source[i];
}
}
Es una promesa muy tonta. ¿Por qué debería hacer una promesa que no aporta ningún beneficio a la persona que llama y sólo limita su implementación?
Ambas son implementaciones perfectamente válidas de la misma función, por lo que todo lo que has hecho es atar una mano detrás de tu espalda innecesariamente.
Además, es una promesa muy superficial que se puede eludir fácilmente (y legalmente).
inline void bytecopyWrapped(char * dest,
const char *source, int count)
{
while(count--)
{
*dest++=*source++;
}
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source,SUPERFLUOUS_CONST int count)
{
bytecopyWrapped(dest, source, count);
}
Mira, lo implementé de esa manera de todos modos a pesar de que prometí no hacerlo, simplemente usando una función contenedora. Es como cuando el malo promete no matar a alguien en una película y ordena a su secuaz que lo mate.
Esas constantes superfluas no valen más que una promesa del malo de una película.
Pero la capacidad de mentir empeora aún más:
Me han informado que es posible no coincidir con const en el encabezado (declaración) y el código (definición) mediante el uso de una const espuria. Los defensores de const-happy afirman que esto es algo bueno ya que permite poner const sólo en la definición.
// Example of const only in definition, not declaration
struct foo { void test(int *pi); };
void foo::test(int * const pi) { }
Sin embargo, lo contrario es cierto... puedes poner una constante espuria sólo en la declaración e ignorarla en la definición. Esto sólo hace que las constantes superfluas en una API sean algo más terrible y una mentira horrible; vea este ejemplo:
struct foo
{
void test(int * const pi);
};
void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
pi++; // I promised in my definition I wouldn't modify this
}
Todo lo que realmente hace la constante superflua es hacer que el código del implementador sea menos legible al obligarlo a usar otra copia local o una función contenedora cuando quiere cambiar la variable o pasar la variable mediante una referencia no constante.
Mira este ejemplo. ¿Cuál es más legible? ¿Es obvio que la única razón para la variable adicional en la segunda función es porque algún diseñador de API agregó una constante superflua?
struct llist
{
llist * next;
};
void walkllist(llist *plist)
{
llist *pnext;
while(plist)
{
pnext=plist->next;
walk(plist);
plist=pnext; // This line wouldn't compile if plist was const
}
}
void walkllist(llist * SUPERFLUOUS_CONST plist)
{
llist * pnotconst=plist;
llist *pnext;
while(pnotconst)
{
pnext=pnotconst->next;
walk(pnotconst);
pnotconst=pnext;
}
}
Ojalá hayamos aprendido algo aquí. La constante superflua es una monstruosidad que abarrota las API, una molestia molesta, una promesa superficial y sin sentido, un obstáculo innecesario y, en ocasiones, conduce a errores muy peligrosos.
Las dos líneas siguientes son funcionalmente equivalentes:
int foo (int a);
int foo (const int a);
Obviamente no podrás modificar a
el cuerpo de foo
si está definido de la segunda manera, pero no hay diferencia desde el exterior.
Donde const
realmente resulta útil es con los parámetros de referencia o de puntero:
int foo (const BigStruct &a);
int foo (const BigStruct *a);
Lo que esto dice es que foo puede tomar un parámetro grande, tal vez una estructura de datos de un tamaño de gigabytes, sin copiarlo. Además, le dice a la persona que llama: "Foo no* cambiará el contenido de ese parámetro". Pasar una referencia constante también permite al compilador tomar ciertas decisiones de rendimiento.
*: A menos que descarte la constancia, pero esa es otra publicación.