Uso de 'const' para parámetros de función

397

¿Hasta dónde llegas const? ¿Simplemente realiza funciones constcuando es necesario o va todo el cerdo y lo usa en todas partes? Por ejemplo, imagine un mutador simple que toma un solo parámetro booleano:

void SetValue(const bool b) { my_val_ = b; }

¿Es eso constrealmente útil? Personalmente, opto por usarlo ampliamente, incluidos los parámetros, pero en este caso me pregunto si vale la pena.

También me sorprendió saber que puede omitir constde los parámetros en una declaración de función pero puede incluirlo en la definición de la 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.

Robar
fuente
Estoy en desacuerdo. El archivo .h también debe tener las definiciones const. De lo contrario, si los parámetros const se pasan a la función, el compilador generará un error, ya que el prototipo en el archivo .h no tiene las definiciones const.
selwyn
10
Estoy de acuerdo. :-) (¡Con la pregunta, no el último comentario!) Si un valor no se debe cambiar en el cuerpo de la función, esto puede ayudar a detener los errores tontos == o =, nunca debe poner constante en ambos, (si se pasa por valor, de lo contrario debe) No es lo suficientemente grave como para entrar en discusiones al respecto!
Chris Huang-Leaver
19
@selwyn: Incluso si pasa un constante int a la función, sin embargo, se va a copiar (ya que no es una referencia), por lo que la constancia no importa.
jalf
1
El mismo debate ocurre en esta pregunta: stackoverflow.com/questions/1554750/…
Parcial
55
Me doy cuenta de que esta publicación tiene un par de años, pero como nuevo programador, me preguntaba esta pregunta y me topé con esta conversación. En mi opinión, si una función no debería cambiar un valor, ya sea una referencia o una copia del valor / objeto, debería ser constante. Es más seguro, se documenta por sí mismo y es más fácil de depurar. Incluso para la función más simple, que tiene una declaración, todavía uso const.

Respuestas:

187

La razón es que la constante para 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. Sin embargo, probablemente sea un mal estilo hacer esto mucho.

Personalmente, tiendo a no usar const excepto para los parámetros de referencia y puntero. Para los objetos copiados, en realidad no importa, aunque puede ser más seguro ya que indica la intención dentro de la función. Es realmente una decisión judicial. Sin embargo, tiendo a usar const_iterator cuando hago un bucle en algo y no tengo la intención de modificarlo, así que supongo que cada uno es suyo, siempre que se mantenga rigurosamente la corrección constante para los tipos de referencia.

Greg Rogers
fuente
57
No puedo estar de acuerdo con la parte del "mal estilo". La eliminación constde los prototipos de funciones tiene la ventaja de que no necesita modificar el archivo de encabezado si decide abandonar la parte constde implementación más adelante.
Michał Górny
44
"Personalmente, tiendo a no usar const excepto para referencia y parámetros de puntero". Tal vez debería aclarar eso a "Tiendo a no usar calificadores superfluos en las declaraciones de funciones, sino usar constdonde hace una diferencia útil".
Deduplicador
3
No estoy de acuerdo con esta respuesta. Me inclino hacia el otro lado y marco los parámetros constsiempre que sea ​​posible; Es más expresivo. Cuando leo el código de otra persona, utilizo pequeños indicadores como este para juzgar cuánto cuidado pusieron en escribir su código junto con cosas como números mágicos, comentarios y uso apropiado del puntero, etc.
Ultimater
44
int getDouble(int a){ ++a; return 2*a; }Prueba esto. Por supuesto, ++ano tiene nada que ver allí, pero se puede encontrar allí en una función larga escrita por más de un programador durante un largo período de tiempo. Sugeriría encarecidamente escribir int getDouble( const int a ){ //... }que generará un error de compilación al encontrar ++a;.
dom_beau
3
Se trata de quién necesita qué información. Proporciona el parámetro por valor para que la persona que llama no necesite saber nada sobre lo que hace (internamente) con él. Entonces escribe class Foo { int multiply(int a, int b) const; }en tu encabezado. En su aplicación te importa que se puede prometer que no altera ay bpor lo que int Foo::multiply(const int a, const int b) const { }tiene sentido aquí. (Nota al margen: tanto la persona que llama como la implementación se preocupan por el hecho de que la función no altera su Fooobjeto, por lo tanto, la constante al final de su declaración)
CharonX
415

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

Incorrecto.

Se trata de auto documentar 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 "constante" todo lo que pueda. Al escribir un código de fuerza industrial, siempre debe suponer que sus compañeros de trabajo son psicópatas que intentan ayudarlo de cualquier manera posible (especialmente porque a menudo es usted 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).

rlerallut
fuente
41
Totalmente de acuerdo. Se trata de comunicarse con las personas y restringir lo que se puede hacer con una variable a lo que se debe hacer.
Len Holgate el
19
He votado a este abajo. Creo que diluye lo que está tratando de indicar con const cuando lo aplica a argumentos simples de paso por valor.
tonylo
26
He votado este arriba. Declarar el parámetro 'const' agrega información semántica al parámetro. Destacan lo que pretendía el autor original del código y esto ayudará a mantener el código a medida que pase el tiempo.
Richard Corden el
13
@tonylo: no lo entiendes. Se trata de marcar una variable local como constante dentro de un bloque de código (que resulta ser una función). Yo haría lo mismo para cualquier variable local. Es ortogonal tener una API que sea constante, lo que de hecho también es importante.
rlerallut
56
Y puede detectar errores dentro de la función: si sabe que no se debe cambiar un parámetro, declararlo const significa que el compilador le dirá si lo modifica accidentalmente.
Adrian
157

A veces (¡con demasiada frecuencia!) Tengo que desenredar el código C ++ de otra persona. Y todos sabemos que el código C ++ de otra persona es un desastre casi por definición :) Entonces, lo primero que hago para descifrar el flujo de datos local es poner constante en cada definición de variable hasta que el compilador comienza a ladrar. Esto significa también argumentos de valor de calificación constante, ya que son solo variables locales sofisticadas inicializadas por la persona que llama.

Ah, desearía que las variables fueran const de forma predeterminada y se requiriera mutable para las variables no const :)

Constantin
fuente
44
"Desearía que las variables fueran constantes por defecto" - ¿un oxímoron? 8-) En serio, ¿cómo "constingar" todo te ayuda a desenredar el código? Si el escritor original cambió un argumento supuestamente constante, ¿cómo sabe que se suponía que la var era una constante? Además, la gran mayoría de las variables (sin argumentos) están destinadas a ser ... variables. Entonces, el compilador debería romperse muy pronto después de comenzar el proceso, ¿no?
ysap
99
@ysap, 1. Marcar const lo más posible me permite ver qué partes se mueven y cuáles no. En mi experiencia, muchos lugareños son de facto constante, no al revés. 2. "Variable constante" / "Variable inmutable" puede sonar como un oxímoron, pero es una práctica estándar en lenguajes funcionales, así como en algunos no funcionales; ver Rust por ejemplo: doc.rust-lang.org/book/variable-bindings.html
Constantin
1
También estándar ahora en algunas circunstancias en c ++; por ejemplo, la lambda [x](){return ++x;}es un error; mira aquí
anatolyg
10
Las variables son " const" por defecto en Rust :)
phoenix
Las variables no necesariamente deben ser asignables para permitir que varíen; el valor con el que se inicializan también puede variar en tiempo de ejecución.
Sphynx
80

Las siguientes dos líneas son funcionalmente equivalentes:

int foo (int a);
int foo (const int a);

Obviamente no podrá modificar aen el cuerpo defoo si se define de la segunda manera, pero no hay diferencia desde el exterior.

Donde constrealmente es útil es con parámetros de referencia o 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 que tiene 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 deseche la constidad, pero esa es otra publicación.

Ben Straub
fuente
3
De eso no se trata esta pregunta; por supuesto, para argumentos referenciados o apuntados, es una buena idea usar const (si el valor referenciado o señalado no se modifica). Tenga en cuenta que no es el parámetro const en su ejemplo de puntero; es a lo que apunta el parámetro.
tml
> Pasar una referencia constante también permite al compilador tomar ciertas decisiones de rendimiento. falacia clásica: el compilador tiene que determinar por sí mismo la constante, la palabra clave const no ayuda con eso gracias al alias de puntero y const_cast
jheriko
73

Los const superfluos adicionales son malos desde un punto de vista API:

Poner constantes superfluos adicionales en su código para los parámetros de tipo intrínsecos pasados ​​por el valor satura su API sin hacer ninguna promesa significativa al llamante o al usuario de la API (solo obstaculiza la implementación).

Demasiados 'const' en una API cuando no son necesarios es como " lobo llorón ", 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 consts adicionales en API es bueno para estos dos primeros puntos, si más parámetros de const son buenos, entonces cada argumento que pueda tener una const, DEBE tener una const. De hecho, si fuera realmente tan bueno, desearía que const sea el valor predeterminado para los parámetros y tenga una palabra clave como "mutable" solo cuando desee cambiar el parámetro.

Así que intentemos poner en constante donde 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. La declaración no solo es más abarrotada y más larga y más difícil de leer, sino que tres de las cuatro palabras clave 'const' pueden ser ignoradas de manera segura por el usuario API. Sin embargo, el uso adicional de 'const' ha hecho que la segunda línea sea potencialmente PELIGROSA.

¿Por qué?

Una lectura errónea rápida del primer parámetro char * const bufferpodría hacerle pensar que no modificará la memoria en el búfer de datos que se pasa; sin embargo, ¡esto no es cierto! El 'const' superfluo puede conducir a suposiciones peligrosas e incorrectas sobre su API cuando se escanea o lee mal rápidamente.


Las constantes superfluas también son malas desde el punto de vista de implementación de 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 es "prometedora" de 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];
       }
}

Esa es una promesa muy tonta de hacer. ¿Por qué debería hacer una promesa que no brinda ningún beneficio a su interlocutor y solo limita su implementación?

Sin embargo, ambas son implementaciones perfectamente válidas de la misma función, por lo que todo lo que has hecho es atar una mano a la espalda innecesariamente.

Además, es una promesa muy superficial que es fácil (y legalmente eludida).

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);
}

Mire, lo implementé de esa manera de todos modos, aunque prometí no hacerlo, solo usando una función de contenedor. Es como cuando el chico malo promete no matar a alguien en una película y le ordena a su secuaz que lo mate.

Esas constantes superfluas no valen más que una promesa de un chico malo de la película.


Pero la capacidad de mentir es aún peor:

Me han aclarado que puede no coincidir const en el encabezado (declaración) y el código (definición) mediante el uso de const espuria. Los defensores de const-happy afirman que esto es algo bueno, ya que le permite poner const solo en la definición.

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

Sin embargo, lo contrario es cierto ... puede poner una constante espuria solo en la declaración e ignorarla en la definición. Esto solo hace que la constante superflua en una API sea más una cosa terrible y una mentira horrible: vea este ejemplo:

class 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 hace const superfluo en realidad es hacer que el código del implementador sea menos legible al obligarlo a usar otra copia local o una función de envoltura cuando quiere cambiar la variable o pasar la variable por referencia no const.

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 arrojó 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í. Const superfluo es una monstruosidad atormentada API, un fastidio molesto, una promesa superficial y sin sentido, un obstáculo innecesario, y ocasionalmente conduce a errores muy peligrosos.

Adisak
fuente
99
¿Por qué los votos negativos? Es mucho más útil si deja un breve comentario en un voto negativo.
Adisak
77
El objetivo de usar el argumento const es hacer que la línea marcada falle (plist = pnext). Es una medida de seguridad razonable mantener inmutable el argumento de la función. Estoy de acuerdo con su punto de que son malas en las declaraciones de funciones (ya que son superfluas), pero pueden cumplir sus propósitos en el bloque de implementación.
touko
23
@Adisak No veo nada malo en su respuesta, per se, pero parece que por sus comentarios se está perdiendo un punto importante. La definición / implementación de la función no es parte de la API, que es solo la declaración de la función . Como ha dicho, declarar funciones con parámetros const no tiene sentido y agrega desorden. Sin embargo, es posible que los usuarios de la API nunca necesiten ver su implementación. Mientras tanto, el implementador puede decidir calificar algunos de los parámetros en la definición de la función SOLAMENTE para mayor claridad, lo cual está perfectamente bien.
jw013
17
@ jw013 es correcto, void foo(int)y void foo(const int)son exactamente la misma función, no sobrecargas. ideone.com/npN4W4 ideone.com/tZav9R La const aquí es solo un detalle de implementación del cuerpo de la función, y no tiene ningún efecto en la resolución de sobrecarga. Deje const fuera de la declaración, para una API más segura y ordenada, pero ponga const en la definición , si tiene la intención de no modificar el valor copiado.
Oktalist
3
@Adisak Sé que esto es viejo, pero creo que el uso correcto para una API pública sería al revés. De esa forma, los desarrolladores que trabajan en los componentes internos no cometen errores, como pi++cuando se supone que no deben hacerlo.
CoffeeandCode
39

const debería haber sido el valor predeterminado en C ++. Me gusta esto :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable
QBziZ
fuente
8
Exactamente mis pensamientos.
Constantin
24
La compatibilidad con C es demasiado importante, al menos para las personas que diseñan C ++, incluso para considerarlo.
44
Interesante, nunca había pensado en eso.
Dan
66
Del mismo modo, unsigneddebería haber sido el valor predeterminado en C ++. Así: int i = 5; // i is unsignedy signed int i = 5; // i is signed.
hkBattousai
25

Cuando codifiqué C ++ para vivir gané todo lo que pude. Usar const es una excelente manera de ayudar al compilador a ayudarte. Por ejemplo, si los valores de retorno de su método pueden salvarlo de errores tipográficos como:

foo() = 42

cuando quisiste decir:

foo() == 42

Si se define foo () para devolver una referencia no constante:

int& foo() { /* ... */ }

El compilador felizmente le permitirá asignar un valor al anónimo temporal devuelto por la llamada a la función. Haciéndolo constante:

const int& foo() { /* ... */ }

Elimina esta posibilidad.

revs Avdi
fuente
66
¿Con qué compilador funcionó eso? GCC da un error al intentar compilar foo() = 42: error: lvalue requerido como operando izquierdo de la asignación
gavrie
Esto es simplemente incorrecto. foo () = 42 es lo mismo que 2 = 3, es decir, un error del compilador. Y devolver una constante no tiene sentido. No hace nada para un tipo incorporado.
Josh
2
Me he encontrado con este uso de const y puedo decirte que al final produce más molestias que beneficios. Sugerencia: const int foo()es de un tipo diferente que int foo(), lo que le trae grandes problemas si está utilizando cosas como punteros de función, sistemas de señal / ranura o boost :: bind.
Mephane
2
He corregido el código para incluir el valor de retorno de referencia.
Avdi
¿No es const int& foo()efectivamente lo mismo que int foo(), debido a la optimización del valor de retorno?
Zantier
15

Hay una buena discusión sobre este tema en los viejos artículos del "Gurú de la semana" en comp.lang.c ++. Moderados aquí .

El artículo correspondiente de GOTW está disponible en el sitio web de Herb Sutter aquí .

Vacío
fuente
1
Herb Sutter es un tipo muy inteligente :-) Definitivamente vale la pena leerlo y estoy de acuerdo con TODOS sus puntos.
Adisak
2
Bueno, buen artículo, pero no estoy de acuerdo con él sobre los argumentos. También los hago constantes porque son como variables, y nunca quiero que nadie haga ningún cambio en mis argumentos.
QBziZ
9

Digo const sus parámetros de valor.

Considere esta función con errores:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

Si el parámetro número fuera constante, el compilador se detendría y nos advertiría del error.


fuente
2
otra forma es usar if (0 == número) ... más ...;
Johannes Schaub - litb
55
@ ChrisHuang-Leaver Horrible no lo es, si hablas como Yoda lo haces: stackoverflow.com/a/2430307/210916
MPelletier
GCC / Clang -Wall te da -Wparentheses, lo que exige que lo hagas "if ((número = 0))" si eso es lo que pretendías hacer. Lo que funciona bien como un sustituto de ser Yoda.
Jetski S-type
8

Utilizo const en parámetros de función que son referencias (o punteros) que son solo [en] datos y no serán modificados por la función. Es decir, cuando el propósito de usar una referencia es evitar copiar datos y no permitir cambiar el parámetro pasado.

Poner const en el parámetro booleano b en su ejemplo solo pone una restricción en la implementación y no contribuye a la interfaz de la clase (aunque generalmente no se recomienda cambiar los parámetros).

La firma de la función para

void foo(int a);

y

void foo(const int a);

es lo mismo, lo que explica su .c y .h

Asaf

Asaf R
fuente
6

Si usa el ->*o.* operadores , es imprescindible.

Te impide escribir algo como

void foo(Bar *p) { if (++p->*member > 0) { ... } }

lo cual casi hice ahora, y que probablemente no haga lo que pretendes.

Lo que pretendía decir era

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

y si hubiera puesto un constintermedio Bar *y p, el compilador me lo habría dicho.

usuario541686
fuente
44
Inmediatamente verificaría una referencia sobre la precedencia del operador cuando estoy a punto de mezclar esa cantidad de operadores (si aún no conozco el 100%), por lo que la OMI no es un problema.
mk12
5

Ah, uno duro. Por un lado, una declaración es un contrato y realmente no tiene sentido pasar un argumento constante por valor. Por otro lado, si observa la implementación de la función, le da al compilador más oportunidades de optimizar si declara un argumento constante.

Nemanja Trifunovic
fuente
5

const no tiene sentido cuando el argumento se pasa por valor, ya que no modificará el objeto del llamante.

Se debe preferir const al pasar por referencia, a menos que el propósito de la función sea modificar el valor pasado.

Finalmente, una función que no modifica el objeto actual (esto) puede, y probablemente debería declararse const. Un ejemplo está abajo:

int SomeClass::GetValue() const {return m_internalValue;}

Es una promesa de no modificar el objeto al que se aplica esta llamada. En otras palabras, puede llamar:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

Si la función no fuera constante, esto generaría una advertencia del compilador.

Dan Hewett
fuente
5

Marcar los parámetros de valor 'const' es definitivamente algo subjetivo.

Sin embargo, en realidad prefiero marcar los parámetros de valor const, como en su ejemplo.

void func(const int n, const long l) { /* ... */ }

El valor para mí es indicar claramente que la función nunca cambia los valores de los parámetros de la función. Tendrán el mismo valor al principio que al final. Para mí, es parte de mantener un estilo de programación muy funcional.

Para una función corta, podría decirse que es una pérdida de tiempo / espacio tener la 'constante' allí, ya que generalmente es bastante obvio que la función no modifica los argumentos.

Sin embargo, para una función más grande, es una forma de documentación de implementación y el compilador la aplica.

Puedo estar seguro de que si hago algún cálculo con 'n' y 'l', puedo refactorizar / mover ese cálculo sin temor a obtener un resultado diferente porque me perdí un lugar donde se cambia uno o ambos.

Como se trata de un detalle de implementación, no necesita declarar los parámetros de valor const en el encabezado, al igual que no necesita declarar los parámetros de función con los mismos nombres que usa la implementación.

Lloyd
fuente
4

Puede ser que esto no sea un argumento válido. pero si incrementamos el valor de una variable constante dentro de un compilador de funciones nos dará un error: " error: incremento del parámetro de solo lectura ". eso significa que podemos usar la palabra clave const como una forma de evitar la modificación accidental de nuestras variables dentro de las funciones (que se supone que no debemos hacer / solo lectura). así que si accidentalmente lo hicimos en el compilador en tiempo de compilación nos lo hará saber. Esto es especialmente importante si no eres el único que está trabajando en este proyecto.

HarshaXsoad
fuente
3

Tiendo a usar const siempre que sea posible. (U otra palabra clave apropiada para el idioma de destino). Lo hago simplemente porque permite que el compilador realice optimizaciones adicionales que de otra forma no podría hacer. Como no tengo idea de cuáles pueden ser estas optimizaciones, siempre lo hago, incluso cuando parece una tontería.

Por lo que sé, el compilador podría ver un parámetro de valor constante y decir: "Oye, esta función no lo modifica de todos modos, así que puedo pasar por referencia y guardar algunos ciclos de reloj". No creo que alguna vez haga algo así, ya que cambia la firma de la función, pero hace el punto. Tal vez hace una manipulación de pila diferente o algo así ... El punto es que no lo sé, pero sí sé que tratar de ser más inteligente que el compilador solo me lleva a avergonzarme.

C ++ tiene algo de equipaje adicional, con la idea de corrección constante, por lo que se vuelve aún más importante.

jdmichal
fuente
Si bien podría ayudar en algunos casos, sospecho que la posibilidad de promover optimizaciones se exagera dramáticamente como un beneficio const. Más bien, es una cuestión de indicar la intención dentro de la implementación y capturar thinkoes más tarde (incrementar accidentalmente la variable local incorrecta, porque no fue así const). Paralelamente, también agregaría que los compiladores son muy bienvenidos a cambiar las firmas de funciones, en el sentido de que las funciones pueden integrarse y, una vez integradas, la forma en que funcionan puede cambiarse; agregar o eliminar referencias, hacer literales de 'variables', etc., todos están dentro de la regla como si
subrayado_d
3

1. La mejor respuesta basada en mi evaluación:

La respuesta de @Adisak es la mejor respuesta aquí según mi evaluación. Tenga en cuenta que esta respuesta es, en parte, la mejor porque también es la mejor respaldada con ejemplos de código real , además de usar un sonido y una lógica bien pensada.

2. Mis propias palabras (de acuerdo con la mejor respuesta):

  1. Para el paso por valor no hay ningún beneficio en agregar const. Todo lo que hace es:
    1. limite el implementador para que tenga que hacer una copia cada vez que quiera cambiar un parámetro de entrada en el código fuente (dicho cambio no tendría efectos secundarios de todos modos ya que lo que se pasa ya es una copia, ya que es paso por valor). Y con frecuencia, el cambio de un parámetro de entrada que se pasa por valor se usa para implementar la función, por lo que agregarlo en consttodas partes puede dificultar esto.
    2. y agregar constinnecesariamente desordena el código con consts en todas partes, alejando la atención de los consts que son realmente necesarios para tener un código seguro.
  2. Sin embargo, cuando se trata de punteros o referencias , constes de vital importancia cuando es necesario, y debe usarse, ya que evita los efectos secundarios no deseados con cambios persistentes fuera de la función, y por lo tanto, cada puntero o referencia debe usarse constcuando el parámetro es solo una entrada, No es una salida. Utilizandoconst solo en parámetros pasados ​​por referencia o puntero tiene el beneficio adicional de hacer realmente obvio qué parámetros son punteros o referencias. Una cosa más es sobresalir y decir "¡Cuidado! ¡Cualquier parámetro constjunto a él es una referencia o un puntero!".
  3. Lo que he descrito anteriormente ha sido con frecuencia el consenso alcanzado en las organizaciones profesionales de software en las que he trabajado, y se ha considerado la mejor práctica. A veces, incluso, la regla ha sido estricta: "nunca use const en parámetros que se pasan por valor, sino que siempre lo use en parámetros pasados ​​por referencia o puntero si son solo entradas".

3) Palabras de Google (de acuerdo conmigo y la mejor respuesta):

(Desde el " Guía de estilo de Google C ++ ")

Para un parámetro de función pasado por valor, const no tiene efecto en el llamador, por lo tanto, no se recomienda en las declaraciones de función. Ver TotW # 109 .

El uso de const en variables locales no se recomienda ni se desaconseja.

Fuente: la sección "Uso de const" de la Guía de estilo de Google C ++: https://google.github.io/styleguide/cppguide.html#Use_of_const . Esta es realmente una sección realmente valiosa, así que lea la sección completa.

Tenga en cuenta que "TotW # 109" significa "Consejo de la semana # 109: significativo consten las declaraciones de funciones" , y también es una lectura útil. Es más informativo y menos prescriptivo sobre qué hacer, y según el contexto anterior a la regla de la Guía de estilo de Google C ++ constcitada anteriormente, pero como resultado de la claridad que proporcionó, la constregla citada arriba se agregó a Google C ++ Guía de estilo.

También tenga en cuenta que aunque estoy citando la Guía de estilo de Google C ++ aquí en defensa de mi posición, NO significa que siempre sigo la guía o siempre recomiendo seguirla. Algunas de las cosas que recomiendan son simplemente extrañas, como su kDaysInAWeekconvención de nomenclatura de estilo para "Nombres constantes" . Sin embargo, todavía es útil y relevante señalar cuando una de las compañías técnicas y de software más exitosas e influyentes del mundo usa la misma justificación que yo y otros como @Adisak hacemos para respaldar nuestros puntos de vista sobre este asunto.

4. El linter de Clang clang-tidy, tiene algunas opciones para esto:

R. Es también digno de mención que la desfibradora de Sonido metálico, clang-tidy, tiene una opción, readability-avoid-const-params-in-decls, descrito aquí , para apoyar la aplicación de una base de código no se utiliza constpara los parámetros de función pase por valor :

Comprueba si una declaración de función tiene parámetros que son const de nivel superior.

Los valores constantes en las declaraciones no afectan la firma de una función, por lo que no deben colocarse allí.

Ejemplos:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

Y aquí hay dos ejemplos más que estoy agregando para completar y aclarar:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B. También tiene esta opción: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. Mi enfoque pragmático de cómo redactaría una guía de estilo al respecto:

Simplemente copiaría y pegaría esto en mi guía de estilo:

[COPIAR / PEGAR INICIO]

  1. Utilice siempre los const parámetros de funciones pasados ​​por referencia o puntero cuando sus contenidos (a lo que apuntan) NO se deben cambiar. De esta manera, se vuelve obvio cuando una variable pasada por referencia o puntero se espera que cambie, porque le faltará const. En este caso de uso constevita efectos secundarios accidentales fuera de la función.
  2. No se recomienda su uso consten los parámetros de función pasados ​​por valor, ya que constno tiene ningún efecto en la persona que llama: incluso si la variable se cambia en la función, no habrá efectos secundarios fuera de la función. Consulte los siguientes recursos para obtener información y justificaciones adicionales:
    1. "Guía de estilo de Google C ++" sección "Uso de const"
    2. "Consejo de la semana # 109: significativo const en las declaraciones de funciones"
    3. Respuesta de desbordamiento de pila de Adisak en "Uso de 'const' para parámetros de función"
  3. " Nunca use el nivel superior const[es decir, consten parámetros pasados ​​por valor ] en parámetros de función en declaraciones que no son definiciones (y tenga cuidado de no copiar / pegar un sin sentido const). No tiene sentido e ignorado por el compilador, es ruido visual , y podría engañar a los lectores "( https://abseil.io/tips/109 , énfasis agregado).
    1. Los únicos constcalificadores que tienen un efecto en la compilación son los que se colocan en la definición de la función, NO los que están en una declaración directa de la función, como en una declaración de función (método) en un archivo de encabezado.
  4. Nunca use el nivel superior const[es decir, consten variables pasadas por valor ] en valores devueltos por una función.
  5. El uso constde punteros o referencias devueltas por una función depende del implementador , ya que a veces es útil.
  6. TODO: aplique algunas de las clang-tidyopciones anteriores con las siguientes opciones:
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

Aquí hay algunos ejemplos de código para demostrar las constreglas descritas anteriormente:

constEjemplos de parámetros:
(algunos se toman prestados de aquí )

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

constEjemplos de tipo de devolución:
(algunos se toman prestados de aquí )

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[COPIA / PEGAR FINAL]

Gabriel Staples
fuente
2

En el caso que mencione, no afecta a las personas que llaman de su API, por lo que no se hace comúnmente (y no es necesario en el encabezado). Solo afecta la implementación de su función.

No es algo particularmente malo, pero los beneficios no son tan buenos dado que no afecta su API y agrega tipeo, por lo que generalmente no se hace.

Luke Halliwell
fuente
2

No uso const para el parámetro de valor pasado. A la persona que llama no le importa si modifica el parámetro o no, es un detalle de implementación.

Lo realmente importante es marcar los métodos como constantes si no modifican su instancia. Haga esto a medida que avanza, porque de lo contrario podría terminar con un montón de const_cast <> o podría encontrar que marcar un método const requiere cambiar mucho código porque llama a otros métodos que deberían haberse marcado const.

También tiendo a marcar variables locales const si no necesito modificarlas. Creo que hace que el código sea más fácil de entender al facilitar la identificación de las "partes móviles".

Aurélien Gâteau
fuente
2

Yo uso const donde puedo. Const para parámetros significa que no deben cambiar su valor. Esto es especialmente valioso cuando se pasa por referencia. const for function declara que la función no debería cambiar los miembros de las clases.

Dror Helper
fuente
2

Para resumir:

  • "Normalmente, el valor de paso constante es inútil y, en el mejor de los casos, engañoso". De GOTW006
  • Pero puede agregarlos en el .cpp como lo haría con las variables.
  • Tenga en cuenta que la biblioteca estándar no usa const. Por ej std::vector::at(size_type pos). Lo que es lo suficientemente bueno para la biblioteca estándar es bueno para mí.
YvesgereY
fuente
2
"Lo que es lo suficientemente bueno para la biblioteca estándar es bueno para mí" no siempre es cierto. Por ejemplo, la biblioteca estándar usa nombres de variables feos como _Tmptodo el tiempo; no quiere esto (en realidad no puede usarlos).
anatolyg
1
@anatolyg este es un detalle de implementación
Fernando Pelliccioni
2
Bien, tanto los nombres de variables como los tipos const en las listas de argumentos son detalles de implementación. Lo que quiero decir es que la implementación de la biblioteca estándar a veces no es buena. A veces, puedes (y deberías) hacerlo mejor. ¿Cuándo se escribió el código para la biblioteca estándar, hace 10 años? Hace 5 años (algunas partes más nuevas) Podemos escribir un mejor código hoy.
anatolyg
1

Si el parámetro se pasa por valor (y no es una referencia), generalmente no hay mucha diferencia si el parámetro se declara como constante o no (a menos que contenga un miembro de referencia, no es un problema para los tipos integrados). Si el parámetro es una referencia o un puntero, generalmente es mejor proteger la memoria referenciada / apuntada, no el puntero en sí (creo que no puede hacer que la referencia en sí misma sea constante, no es que importe mucho ya que no puede cambiar el árbitro) . Parece una buena idea proteger todo lo que pueda como constante. Puede omitirlo sin temor a cometer un error si los parámetros son solo POD (incluidos los tipos incorporados) y no hay posibilidad de que cambien más a lo largo del camino (por ejemplo, en su ejemplo, el parámetro bool).

No sabía sobre la diferencia de declaración de archivo .h / .cpp, pero tiene sentido. En el nivel de código de máquina, nada es "constante", por lo que si declara una función (en .h) como no constante, el código es el mismo que si lo declara como constante (aparte de las optimizaciones). Sin embargo, le ayuda a alistar al compilador que no cambiará el valor de la variable dentro de la implementación de la función (.ccp). Puede ser útil en el caso cuando está heredando de una interfaz que permite el cambio, pero no necesita cambiar el parámetro para lograr la funcionalidad requerida.

Atila
fuente
0

No pondría constantes parámetros como ese: todos ya saben que un valor booleano (en oposición a un valor booleano) es constante, por lo que agregarlo hará que la gente piense "espera, ¿qué?" o incluso que estás pasando el parámetro por referencia.

Khoth
fuente
44
a veces querrá pasar un objeto por referencia (por razones de rendimiento) pero no cambiarlo, por lo que const es obligatorio entonces. Mantener todos estos parámetros, incluso bools, const sería una buena práctica, haciendo que su código sea más fácil de leer.
gbjbaanb
0

Lo que hay que recordar con const es que es mucho más fácil hacer las cosas const desde el principio, que tratar de ponerlas más tarde.

Use const cuando desee que algo no cambie: es una pista adicional que describe lo que hace su función y qué esperar. ¡He visto muchas API C que podrían funcionar con algunas de ellas, especialmente las que aceptan cadenas c!

Estaría más inclinado a omitir la palabra clave const en el archivo cpp que el encabezado, pero como tiendo a cortarlas y pegarlas, se guardarán en ambos lugares. No tengo idea de por qué el compilador lo permite, supongo que es una cosa del compilador. La mejor práctica es definitivamente poner su palabra clave const en ambos archivos.

gbjbaanb
fuente
No entiendo esto en absoluto. ¿Por qué estaría inclinado a omitirlo en el archivo cpp (definición de función)? Ahí es donde realmente significa algo y puede detectar errores. ¿Por qué crees que es una buena práctica poner constante en ambos lugares? En el archivo de encabezado (declaración de función), no significa nada y abarrota la API. Quizás haya un pequeño valor para que el declive y el defn se vean exactamente iguales, pero me parece que es un beneficio realmente menor en comparación con el problema de saturar la API.
Don Hatch
@DonHatch 8 años después, wow. De todos modos, como dijo el OP "También me sorprendió saber que puede omitir const de los parámetros en una declaración de función, pero puede incluirlo en la definición de la función".
gbjbaanb
0

Realmente no hay razón para hacer un parámetro de valor "const" ya que la función solo puede modificar una copia de la variable de todos modos.

La razón para usar "const" es si está pasando algo más grande (por ejemplo, una estructura con muchos miembros) por referencia, en cuyo caso se asegura de que la función no pueda modificarlo; o más bien, el compilador se quejará si intenta modificarlo de la manera convencional. Impide que se modifique accidentalmente.

MarkR
fuente
0

El parámetro Const es útil solo cuando el parámetro se pasa por referencia, es decir, ya sea referencia o puntero. Cuando el compilador ve un parámetro const, se asegura de que la variable utilizada en el parámetro no se modifique dentro del cuerpo de la función. ¿Por qué alguien querría hacer un parámetro de valor como constante? :-)


fuente
Por muchos motivos. Hacer un parámetro por valor constestablece claramente: 'No necesito modificar esto, así que estoy declarando eso. Si intento modificarlo más tarde, me da un error en tiempo de compilación para que pueda corregir mi error o desmarcar como const". Por lo tanto, es una cuestión de código de higiene y seguridad. Por todo lo que se necesita para agregar a los archivos de implementación, debería ser algo que la gente haga como un reflejo puro, IMO.
underscore_d
0

Como los parámetros se pasan por valor, no hace ninguna diferencia si especifica const o no desde la perspectiva de la función de llamada. Básicamente, no tiene ningún sentido declarar los parámetros de pasar por valor como const.


fuente
0

Todos los consts en sus ejemplos no tienen ningún propósito. C ++ es paso por valor por defecto, por lo que la función obtiene copias de esos ints y booleanos. Incluso si la función los modifica, la copia de la persona que llama no se ve afectada.

Así que evitaría concursos adicionales porque

  • Son reductores
  • Abarrotan el texto
  • Me impiden cambiar el valor pasado en los casos en que podría ser útil o eficiente.
AShelly
fuente
-1

Sé que la pregunta está "un poco" desactualizada, pero a medida que me cruzo con ella, alguien más puede hacerlo en el futuro ... ... aún dudo que el pobre tipo haga una lista aquí para leer mi comentario :)

Me parece que todavía estamos demasiado limitados a la forma de pensar al estilo C. En el paradigma OOP jugamos con objetos, no con tipos. El objeto const puede ser conceptualmente diferente de un objeto no const, específicamente en el sentido de const lógico (en contraste con bitst-const). Por lo tanto, incluso si la corrección de los parámetros de la función es (quizás) un exceso de cuidado en el caso de los POD, no lo es en el caso de los objetos. Si una función funciona con un objeto const, debería decirlo. Considere el siguiente fragmento de código

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

ps .: puede argumentar que la referencia (const) sería más apropiada aquí y le da el mismo comportamiento. Pues bien. Simplemente dando una imagen diferente de lo que pude ver en otro lado ...

PavDub
fuente