2009-10-12 11 views
22

En las siguientes funciones de C++:C++ const palabra clave: ¿usar liberalmente?

void MyFunction(int age, House &purchased_house) 
{ 
    ... 
} 


void MyFunction(const int age, House &purchased_house) 
{ 
    ... 
} 

¿Qué es mejor?

En ambos, 'edad' se pasa por valor. Me pregunto si la palabra clave 'const' es necesaria: me parece redundante, pero también útil (como indicación adicional de que la variable no cambiará).

¿Alguien tiene alguna opinión sobre cuáles (si hay alguno) de los anteriores son mejores?

+3

Una pregunta similar estaba en esta publicación de StackOverflow: http://stackoverflow.com/questions/117293/use-of-const-for-function-parameters – Void

Respuesta

22

Esto, en mi humilde opinión, está sobreutilizando. Cuando dice 'const int age, ...' lo que realmente dice es "no puede cambiar incluso la copia local dentro de su función". Lo que hace es hacer que el código del programador sea menos legible forzándolo a usar otra copia local cuando quiere cambiar la edad/pasarla por referencia no constante.

Cualquier programador debe estar familiarizado con la diferencia entre pase por referencia y paso por valor igual que cualquier programador debe entender 'const'.

+42

Obligar al programador a usar otro nombre de variable local en lugar de reutilizar el parámetro de entrada formal es algo bueno. Reutilizar el parámetro para un nuevo propósito asignándole un nuevo valor hace que el código sea menos legible al darle ese nombre con múltiples propósitos. Algunos idiomas no permiten esto, ejemplo C#. –

+17

-1. Usar const en realidad ayuda a la depuración aquí. Al forzar al programador a usar una variable LOCAL, le ayuda a ella (y a sus lectores) a comprender realmente que los cambios no se propagarán fuera del método/función. –

+6

Todo se reduce a la pregunta "¿cuál es el nivel de los LECTORES en la programación y a qué están acostumbrados?" Para un principiante, las excepciones, la semántica de referencia e incluso el reparto implícito pueden parecer confusos e ilegibles. Cuando escribo código trato de ser claro. No obstante, no pretendo ser entendido por cada principiante. Es más importante para mí evitar el emparejamiento de patrones (mencionado por litb e idimba en sus respuestas) que el programador experimentado puede hacer erróneamente (como probablemente lo haga). y espero que el lector entienda pasar por copia. –

7

Consulte Meyers y 'Effective C++' en esto, y use const liberalmente, especialmente con la semántica de paso por referencia.

En este caso de variables atómicas, la eficiencia no importa, pero la claridad del código aún se beneficia.

+4

No estoy de acuerdo en el caso de un tipo de paso por valor primitivo. Haría una doble toma si veía eso en el código, ya que el destinatario no puede cambiar el valor (desde el punto de vista de la persona que llama). Me pregunto si hay algún error tipográfico, si la persona que escribe el código simplemente no entiende pasar de valor. – Dolphin

+2

@Dolphin: Entonces deberías leer el excelente tratamiento de litb (como siempre) sobre el tema, aprender y dejar de pensar. Si el código se siente "natural" o "usado" fue un criterio de corrección, nadie habría comenzado a utilizar el STL para todos estos divertidos iteradores que se sentían tan poco naturales a primera vista. – sbi

+0

Si hubieran evitado eso, tal vez tendríamos soporte decente para los rangos en C++ por ahora ;-p –

8

No solo indica que no cambiará, evitará que su código crea que puede y será capturado en tiempo de compilación.

62

En primer lugar, es sólo un detalle de implementación, y si se puso const allí, no lo ponga en el conjunto de declaración (cabecera). Sólo ponerlo en el archivo de implementación:

// header 
void MyFunction(int age, House &purchased_house); 

// .cpp file 
void MyFunction(const int age, House &purchased_house); 
{ 
    ... 
} 

Sea o no un parámetro es const en una definición es puramente un detalle de implementación, y no debe ser parte de la interfaz.

No he visto este tipo de cosas seguido, y tampoco hago esto. Tener el parámetro const me confunda más a menudo que ayuda, porque me inmediatamente patrón-match-fallar a "const int & edad" :) La cuestión, por supuesto, es totalmente diferente de tener const en otro nivel:

// const is a *good* thing here, and should not be removed, 
// and it is part of the interface 
void MyFunction(const string &name, House &purchased_house); 
{ 
    ... 
} 

En este caso, la const afectará si la función puede cambiar el argumento de la persona que llama. Const en este sentido debe utilizarse con la mayor frecuencia posible, ya que puede ayudar a garantizar la corrección del programa y mejorar la auto-documentación del código.

+1

Const no es un detalle de implementación en el caso de 'const int & age'. – Brian

+9

@Brian, no he dicho eso. La pregunta es acerca de cuándo pasa el argumento por valor. En este caso, es un detalle de implementación cuando pones const en el nivel superior y haces que el parámetro consista en ello. –

+0

No olvide que puede sobrecargar una función miembro solo cambiando su estado (¿puede decir esto?) –

3

Una ventaja de utilizar const es que no se puede cambiar accidentalmente el valor de age en medio de MyFunction (en caso de haberla olvidado, no se pasan por referencia). Una "desventaja" es que no puede reciclar age con código como foo.process(++age);.

+0

'foo.process (++ age)' debería estar bien ya que 'const' se aplica a lo que' process' puede al argumento: 'int age' es mutable ... es _converted_ a' const int' cuando se accede dentro de la función. –

+0

@ D.Shawley: dije 'foo.proceso (++ edad); ', no' MyFunction (++ edad, barra); ' – Brian

5

Tiene la razón, el único propósito de "const int" es que la edad no se puede cambiar. Sin embargo, esto puede ser muy confuso para la mayoría de los programadores. Entonces, si este enfoque no se usa ampliamente en su código, aconsejaría omitir const.

+3

" el único propósito de "const int" es que la edad no se puede cambiar "Estás diciendo esto como si fuera un poco pequeña cosa. La capacidad de IMO para que el compilador compruebe la corrección de const es una de las mayores fortalezas de C++. – sbi

+5

Estoy todo por escribir un código comprensible, pero no a cambio de perder una verificación en tiempo de compilación. Dicho esto, si este enfoque "no se usa ampliamente en su código", diría que su equipo necesita alguna educación y/o mejores revisiones de códigos para que * se * utilice ampliamente. – JeffH

+1

Casi parece que pasar por valor debe ser const por defecto, ¿no? Y, sin embargo, esto no es una construcción que veas en código real muy a menudo; de hecho, no creo haberla visto nunca. –

37

Recomiendo leer Herb Sutter. Exceptional C++. Hay un capítulo "Const-Correctness".

"De hecho, para el compilador, la firma de la función es la misma ya sea que incluya esta constante delante de un parámetro de valor o no."

Esto significa que esta firma

void print(int number); 

es efectivamente el mismo que esto:

void print(int const number); 

lo tanto, para que el compilador no hay ninguna diferencia cómo declara una función. Y no puede sobrecargarlo poniendo la palabra clave const delante de un parámetro pass by value.

Lea más, qué recomienda Herb Sutter:

"Evite los parámetros const pass-by-value en declaraciones de funciones. Aún hacen la const parámetro en la definición de la misma función si no se modificará "

Se recomienda a evitar esto:.

void print(int const number); 

Debido a que const es confuso, verbosa y redundante.

Pero en la definición, se debe hacer eso (si no va a cambiar el parámetro):

void print(int const number) 
{ 
    // I don't want to change the number accidentally here 
    ... 
} 

Por lo tanto, se asegurará de que incluso después de 1000 líneas del cuerpo de la función siempre tenga el número intacto. El compilador le prohibirá pasar el número como referencia no const a otra función.

+1

Creo que la Const-Correction of Exceptional C++ que mencionó es una de las que se encuentran en: http://www.gotw.ca/gotw/006.htm – Flexo

+2

Sí, todo el libro se basa en gotw. Pero el libro lo explica mejor, IMO. – Vanuan

2

Tener un tipo primitivo que se pasa por valor const es bastante inútil. Pasar un const a una función generalmente es útil como un contrato con la persona que llama para que el funciton no cambie el valor. En este caso, como el int se pasa por valor, la función no puede realizar ningún cambio que sea visible fuera de la función.

Por otro lado, rreferences y tipos de objetos no triviales siempre deben usar const si no van a hacer ningún cambio en el objeto. En teoría, esto podría permitir cierta optimización, pero la gran ganancia es el contrato que mencioné anteriormente. La desventaja es, por supuesto, que puede hacer que su interfaz sea mucho más grande y dificultar la adaptación a un sistema existente (o con una API de terceros que no use const en todas partes).

6

Bueno, como ya se ha dicho, desde el punto de vista del lenguaje C++, el const anterior no tiene ningún efecto en la firma de la función, es decir, el tipo de función permanece igual independientemente de si el const está allí o no. El único efecto que tiene en el lenguaje abstracto C++ es que no puede modificar este parámetro dentro del cuerpo de la función.

Sin embargo, en el nivel inferior, el modificador const aplicado al valor del parámetro puede tener algunos beneficios de optimización, dado un compilador suficientemente inteligente. Considere dos funciones con el mismo conjunto de parámetros (para simplificar)

int foo(const int a, const double b, const long c) { 
    /* whatever */ 
} 

void bar(const int a, const double b, const long c) { 
    /* whatever */ 
} 

Digamos que en alguna parte del código que se denominan de la siguiente manera

foo(x, d, m); 
bar(x, d, m); 

Normalmente, los compiladores preparan marco de pila con argumentos antes de llamar a una función . En este caso, la pila generalmente se preparará dos veces: una para cada llamada. Pero un compilador inteligente podría darse cuenta de que dado que estas funciones no cambian sus valores de parámetros locales (declarados con const), el conjunto de argumentos preparado para la primera llamada se puede reutilizar de forma segura para la segunda llamada. Por lo tanto, puede preparar la pila solo una vez.

Esta es una optimización bastante rara y oscura, que solo puede funcionar cuando se conoce la definición de la función en el punto de la llamada (la misma unidad de traducción o un compilador avanzado de optimización global), pero a veces puede vale la pena mencionar.

No es correcto decir que es "inútil" o "no tiene ningún efecto", aunque con un compilador típico este podría ser el caso.

Otra consideración que vale la pena mencionar es de naturaleza diferente. Existen estándares de codificación que requieren que los codificadores no cambien los valores de los parámetros iniciales, como en "no use parámetros como variables locales ordinarias, los valores de los parámetros deben permanecer sin cambios a lo largo de la función". Esto tiene sentido, ya que a veces hace que sea más fácil determinar qué valores de parámetro se le dio originalmente a la función (mientras estaba en el depurador, dentro del cuerpo de la función). Para ayudar a hacer cumplir este estándar de codificación, las personas pueden usar los especificadores const en los valores de los parámetros. Si vale la pena o no es una pregunta diferente ...

+1

Me temo que esta optimización solo será posible si el compilador es capaz de descubrir que dentro de la llamada a 'foo' nada va a cambiar' x', 'd', y' m' - que es más difícil que tú podría pensar. – sbi

+3

No, te perdiste el punto. Una vez que declaras el parámetro como 'const' (como en mi ejemplo), el compilador no tiene que "descubrir" nada. Puede suponer con seguridad que dentro de 'foo' nada puede cambiar legalmente el valor del argumento, que es el punto de usar 'const' allí. Si de alguna manera logras "piratear" la promesa dada al compilador por 'const', es tu culpa y el compilador no es responsable de atrapar eso. El comportamiento no está definido y todas las apuestas están desactivadas. Así es como funciona 'const' y siempre funcionó en C/C++. – AnT

+4

Creo que sbi tiene aliasing en mente. Para todo lo que sabe el compilador, la implementación de foo tiene acceso a un puntero a x (quizás esté almacenado en un global o alcanzado a través de algún otro parámetro). foo podría escribir sobre eso, con el efecto secundario de modificar x, aunque a (la copia de foo de x) es const. Entonces el valor que se debe pasar a la barra es el nuevo valor de x, no el anterior que se pasó a foo. A menos que el compilador pueda descartar esto, no puede pasar la copia potencialmente "obsoleta" de x como el parámetro a la barra. Por lo tanto, es más difícil de lo que piensas, porque el aliasing es generalmente difícil de descartar. –

3

Segunda variante es mejor. En el primero, puede cambiar accidentalmente la edad variable.

+0

Quizás necesite cambiarlo sin crear otra variable. – Justicle

+1

@Justicle: ¿Por qué? Su programa será mucho más claro si define una nueva variable y la modifica. El compilador debe optimizarlo para que obtenga el mismo ensamblado compilado, pero es más fácil de leer y mantener (por ejemplo, más adelante puede descubrir que necesita el valor del parámetro original al final de la función). – Ethan

4

Debe utilizar const en un parámetro si (y sólo si) se usaría en cualquier otra variable local que no será modificado:

const int age_last_year = age - YEAR; 

A veces es útil para marcar las variables locales const cuando sea posible , ya que significa que puede mirar la declaración y saber que ese es el valor, sin pensar en el código intermedio. Puede cambiarlo fácilmente en el futuro (y asegúrese de no haber roto el código en la función, y tal vez cambiar el nombre de la variable si ahora representa algo ligeramente diferente, que es modificable, en comparación con el anterior sin cambios cosa).

En contra de eso, hace que el código sea más detallado, y en una función corta, casi siempre es muy obvio qué variables cambian y cuáles no.

Así que, ya sea:

void MyFunction(const int age, House &purchased_house) 
{ 
    const int age_last_year = age - YEAR; 
} 

o:

void MyFunction(int age, House &purchased_house) 
{ 
    int age_last_year = age - YEAR; 
} 
2

Éstos son algunos grandes artículos y un libro que encontré explicando las ventajas del uso de const:


¿Puedo proponer la siguiente máxima para usted?

Si un objeto/variable puede calificarse ha sido constante, debería serlo. En el peor de los casos, no costará nada. En el mejor de los casos, documentará el rol del objeto/variable en su código y le permitirá al compilador la oportunidad de optimizar su código aún más.

Algunos compiladores descuidan explotar el potencial de optimización con el uso de 'const' y eso ha llevado a muchos expertos a descuidar el uso de parámetros constantes por valor cuando podría ser utilizado. Esta práctica requiere más rigor, pero no será perjudicial. En el peor de los casos, no pierde nada, pero tampoco gana nada y, en el mejor de los casos, gana con este enfoque.


Para los que no parece entender la utilidad de un const en un parámetro de valor de una función/método ... aquí es un ejemplo corto que explica por qué:

.cpp

void WriteSequence(int const *, const int); 

int main() 
{ 
    int Array[] = { 2, 3, 4, 10, 12 }; 
    WriteSequence(Array, 5); 
} 


#include <iostream> 
using std::cout; 
using std::endl; 
void WriteSequence(int const *Array, const int MAX) 
{ 
    for (int const * i = Array; i != Array + MAX; ++i) 
     cout << *i << endl; 
} 

¿Qué hubiera pasado si hubiese quitado la const delante de int MAX y hubiera escrito MAX + 1 dentro de esta manera?

void WriteSequence(int Array[], int MAX) 
{ 
    MAX += MAX; 
    for (int * i = Array; i != Array + MAX; ++i) 
     cout << *i << endl; 
} 

¡Bien, su programa fallará! Ahora, ¿por qué alguien escribiría "MAX + = MAX"? ? Tal vez un error humano, tal vez el programador no se sentía bien ese día o tal vez el programador simplemente no sabía cómo escribir el código C/C++. Si hubieras tenido const, ¡el código ni siquiera se hubiera compilado!

¡El código de seguridad es un buen código y no cuesta nada agregar "const" cuando lo tiene!


Aquí es una respuesta de un puesto diferente para una pregunta muy similar:

"const no tiene sentido cuando el argumento se pasa por valor, ya que no será la modificación objeto de la persona que llama. "

Wrong.

Se trata de auto-documentar su código y sus suposiciones.

Si el código tiene muchas personas que trabajan en él y sus funciones son no trivial entonces usted debe marcar "const" cualquier y todo lo que lata. Al escribir el código de resistencia industrial, siempre debe suponer que sus compañeros de trabajo son psicópatas intentando para obtener todo lo que puedan (especialmente porque a menudo es usted mismo en el futuro).

Además, como alguien ha mencionado anteriormente , podría ayudar al compilador optimizar las cosas un poco (aunque es una posibilidad muy remota ).

Aquí está el enlace: answer.

+0

Bueno, estoy/estamos de acuerdo en que * no * no tiene sentido escribir const para los parámetros de valor. También puede agregar seguridad. ¿Por qué las personas escriben código C sin procesar, cuando pueden usar Java y ser "más seguros"? Porque C es rápido, y C es de bajo nivel. La seguridad no lo es todo; hay otras preocupaciones como la legibilidad, la claridad. Su punto principal en esta respuesta es que "const" es útil, creo que todos estamos de acuerdo. Pero pegar "const" * en cualquier lugar * - eso es lo que no estamos de acuerdo. No acabo de obtener tus dos primeros enlaces: hablan de "const" en general, no de "const" para parámetros de valor en el toplevel en particular –

+0

Y creo que esta discusión nunca terminará. Usted tiene su opinión, que tiene un fondo bien reflejado, y tengo mi opinión, que también tiene un fondo bien reflejado. Ninguno de nosotros, sospecho, se "convertirá" a la opinión del otro, y sospecho que la mayoría de las otras personas involucradas en esta discusión. –

+1

@litb: para el primer enlace eche un vistazo a la conclusión y para el segundo enlace dice poner un const cuando pueda. Es más o menos un estándar de donde vengo para usar const y terminará haciendo que mi código sea más limpio. – Partial