c++ - dev - Uso de ''const'' para parámetros de función.
const in c (30)
¿Qué tan lejos vas con const
? ¿Hace funciones const
cuando es necesario o va todo el cerdo y lo usa en todas partes? Por ejemplo, imagine un mutador simple que toma un único parámetro booleano:
void SetValue(const bool b) { my_val_ = b; }
¿Es esa const
realmente útil? Personalmente opto por usarlo extensivamente, incluidos los parámetros, pero en este caso me pregunto si vale la pena.
También me sorprendió saber que puede omitir const
de los parámetros en una declaración de función, pero puede incluirla 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.
Si usa los operadores ->*
o .*
, Es una necesidad.
Te impide escribir algo como
void foo(Bar *p) { if (++p->*member > 0) { ... } }
que casi lo hice en este momento, y que probablemente no hace lo que pretendes.
Lo que pretendía decir era
void foo(Bar *p) { if (++(p->*member) > 0) { ... } }
y si hubiera puesto una const
entre Bar *
y p
, el compilador me habría dicho eso.
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 completo casi por definición :) Por lo tanto, lo primero que hago para descifrar el flujo de datos local es poner const en cada definición de variable hasta que el compilador comienza a ladrar. Esto también implica argumentos de valor de calificación constante, ya que son solo variables locales sofisticadas iniciadas por el llamante.
Ah, me gustaría que las variables fueran const de forma predeterminada y mutable fuera necesaria para las variables no const :)
Ah, una dura. 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 posibilidades de optimizar si declara una constante de argumento.
Cuando codifiqué C ++ para ganarme la vida, consteé todo lo que pude. Usar const es una excelente manera de ayudar al compilador a que te ayude. Por ejemplo, la constancia de los valores de retorno de su método puede salvarlo de errores tipográficos como:
foo() = 42
cuando quisiste decir
foo() == 42
Si foo () está definido para devolver una referencia no constante:
int& foo() { /* ... */ }
El compilador le permitirá asignar un valor al temporal anónimo devuelto por la llamada a la función. Haciéndolo const
const int& foo() { /* ... */ }
Elimina esta posibilidad.
En el caso que mencionas, no afecta a quienes llaman a tu API, por lo que no se hace comúnmente (y no es necesario en el encabezado). Solo afecta la implementación de tu función.
No es particularmente malo hacer esto, pero los beneficios no son tan buenos ya que no afectan su API y agregan escritura, por lo que generalmente no se hace.
La razón es que const 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, es probable que sea un estilo malo hacer esto mucho.
Personalmente tiendo a no usar const, excepto para los parámetros de referencia y puntero. Para los objetos copiados realmente no importa, aunque puede ser más seguro ya que señala la intención dentro de la función. Es realmente una llamada de juicio. Sin embargo, tiendo a usar const_iterator cuando hago bucles en algo y no pretendo modificarlo, así que supongo que cada uno es suyo, siempre que se mantenga rigurosamente la corrección constante para los tipos de referencia.
Las constantes superfluas son malas desde un punto de vista API:
Poner constantes superfluos en su código para los parámetros de tipo intrínseco pasados por el valor desordena su API sin hacer una promesa significativa para el usuario que llama o para la API (solo obstaculiza la implementación).
Demasiado ''const'' en una API cuando no se necesita es como " lobo llorando ", eventualmente la gente comenzará a ignorar ''const'' porque está en todas partes y no significa nada la mayor parte del tiempo.
El argumento "reductio ad absurdum" a consts adicionales en API es bueno porque estos dos primeros puntos serían si más parámetros const son buenos, entonces cada argumento que pueda tener una const, DEBERÍA tener una const. De hecho, si fuera realmente tan bueno, querría que const fuera el valor predeterminado para los parámetros y tuviera una palabra clave como "mutable" solo cuando quiera cambiar el parámetro.
Así que intentemos poner const en 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 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 mala lectura rápida del primer parámetro char * const buffer
puede hacerle pensar que no modificará la memoria en el buffer de datos que se pasa; sin embargo, esto no es cierto. El ''const'' superfluo 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 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 "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];
}
}
Esa es una promesa muy tonta de hacer. ¿Por qué debería hacer una promesa que no beneficia en absoluto 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 está atado una mano a la espalda innecesariamente.
Además, es una promesa muy superficial que es fácil (y legalmente burlada).
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 todos modos, aunque prometí no hacerlo, solo usando una función de envoltura. Es como cuando el 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 malo de la película.
Pero la capacidad de mentir se pone aún peor:
Se me ha informado que puede no coincidir la constante en el encabezado (declaración) y el código (definición) utilizando constantes no esenciales. 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 constancia falsa solo en la declaración e ignorarla en la definición. Esto solo hace que la constante superflua en una API sea más de 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 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 de envoltorio cuando quiere cambiar la variable o pasar la variable por 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 lanzó 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;
}
}
Esperemos que hayamos aprendido algo aquí. Superfluous const es una irritación por la API, una molestia molesta, una promesa superficial y sin sentido, un obstáculo innecesario y, en ocasiones, conduce a errores muy peligrosos.
Las siguientes dos líneas son funcionalmente equivalentes:
int foo (int a);
int foo (const int a);
Obviamente, no podrás modificar a
en el cuerpo de foo
si está definida de la segunda manera, pero no hay diferencia desde el exterior.
Donde realmente es útil const
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 tenga 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 que el compilador tome ciertas decisiones de rendimiento.
*: A menos que deseche la constancia, pero esa es otra publicación.
Marcando los parámetros de valor ''const'' es definitivamente una cosa subjetiva.
Sin embargo, en realidad prefiero marcar los parámetros de valor const, como en su ejemplo.
void func(const int n, const long l) { /* ... */ }
Para mí, el valor indica 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 ''const'' 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 realizo algunos cálculos con ''n'' y ''l'', puedo refactorizar / mover ese cálculo sin temor a obtener un resultado diferente porque perdí un lugar donde uno o ambos cambian.
Dado que es 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 utiliza la implementación.
No pondría constantes en parámetros como ese: todos ya saben que un booleano (en lugar de un 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.
No uso const para parametere 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 que es realmente importante es marcar los métodos como constantes si no modifican su instancia. Haga esto a medida que avanza, ya que 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 una gran cantidad de código porque llama a otros métodos que deberían haberse marcado const.
También tiendo a marcar constantes locales si no necesito modificarlas. Creo que hace que el código sea más fácil de entender al hacer que sea más fácil identificar las "partes móviles".
Para resumir:
- "Normalmente, el paso por valor de const es inútil y, en el mejor de los casos, engañoso". Desde here
- Pero puede agregarlos en el .cpp como lo haría con las variables.
- Tenga en cuenta que la biblioteca estándar no utiliza const. Por ejemplo,
std::vector::at(size_type pos)
. Lo que es suficientemente bueno para la biblioteca estándar es bueno para mí.
Puede ser que este no sea un argumento válido. pero si incrementamos el valor de una variable const dentro de un compilador de funciones, nos daremos un error: " error: incremento del parámetro de solo lectura ". así que 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 (lo que se supone que no debemos / solo lectura). así que si lo hicimos accidentalmente en el compilador en tiempo de compilación nos lo haremos saber. Esto es especialmente importante si no eres el único que está trabajando en este proyecto.
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 a, no el puntero en sí (creo que no puede hacer que la referencia en sí sea constante, no es que importe tanto como no puede cambiar el árbitro) . Parece una buena idea proteger todo lo que puedas como const. Puede omitirlo sin temor a equivocarse si los parámetros son solo POD (incluidos los tipos incorporados) y no hay posibilidad de que cambien más a lo largo de la carretera (por ejemplo, en su ejemplo, el parámetro bool).
No sabía acerca de la diferencia de declaración de archivo .h / .cpp, pero tiene sentido. En el nivel del código de máquina, nada es "const", por lo que si declara una función (en .h) como no constante, el código es el mismo que si lo declara como const (optimizaciones aparte). Sin embargo, le ayuda a alistar al compilador para que no cambie el valor de la variable dentro de la implementación de la función (.ccp). Puede ser útil en el caso de que esté heredando de una interfaz que permita el cambio, pero no necesita cambiar el parámetro para lograr la funcionalidad requerida.
Sobre las optimizaciones del compilador: http://www.gotw.ca/gotw/081.htm
Tiendo a usar const siempre que sea posible. (U otra palabra clave apropiada para el idioma de destino). Hago esto simplemente porque le permite al compilador realizar optimizaciones adicionales que de otra forma no podría realizar. 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 está modificando de todos modos, así que puedo pasar por referencia y guardar algunos ciclos de reloj". No creo que alguna vez hiciera 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, no lo sé, pero sé que tratar de ser más inteligente de lo que el compilador solo hace que me avergüencen.
C ++ tiene algo de equipaje adicional, con la idea de la corrección constante, por lo que se vuelve aún más importante.
Uso const en los parámetros de la función que son referencias (o punteros) que son solo datos [in] 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 boolean 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 tu .c y .h
Asaf
Yo digo const sus parámetros de valor.
Considera esta función de buggy:
bool isZero(int number)
{
if (number = 0) // whoops, should be number == 0
return true;
else
return false;
}
Si el parámetro número fuera const, el compilador se detendría y nos avisaría del error.
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 para function declara que la función no debe cambiar los miembros de las clases.
const debería haber sido el predeterminado en C ++. Me gusta esto :
int i = 5 ; // i is a constant
var int i = 5 ; // i is a real variable
const no tiene sentido cuando el argumento pasa por valor ya que no modificará el objeto de la persona que llama.
Se debe preferir const cuando se pasa 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 ser declarada const. A continuación se muestra un ejemplo:
int SomeClass::GetValue() const {return m_internalValue;}
Esta es una promesa de no modificar el objeto al que se aplica esta llamada. En otras palabras, puedes llamar a:
const SomeClass* pSomeClass;
pSomeClass->GetValue();
Si la función no era constante, esto daría como resultado una advertencia del compilador.
"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, debe marcar "const" cualquiera y todo lo que pueda. Al escribir un código de fuerza industrial, siempre debe asumir que sus compañeros de trabajo son psicópatas que intentan obtener lo que pueden (especialmente porque 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).
Al ser un programador de VB.NET que necesita usar un programa C ++ con más de 50 funciones expuestas, y un archivo .h que usa esporádicamente el calificador const, es difícil saber cuándo acceder a una variable utilizando ByRef o ByVal.
Por supuesto, el programa le avisa al generar un error de excepción en la línea donde cometió el error, pero luego necesita adivinar cuál de los parámetros 2-10 es incorrecto.
Así que ahora tengo la desagradable tarea de tratar de convencer a un desarrollador de que realmente deben definir sus variables (en el archivo .h) de una manera que permita un método automatizado de crear fácilmente todas las definiciones de funciones de VB.NET. Luego dirán con aire de suficiencia: "lee la ... documentación".
He escrito un script awk que analiza un archivo .h, y crea todos los comandos de la función Declare, pero sin un indicador de qué variables son R / O vs R / W, solo hace la mitad del trabajo.
EDITAR:
Alentando a otro usuario, estoy agregando lo siguiente;
Aquí hay un ejemplo de una entrada (h) pobremente formada (IMO);
typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );
El VB resultante de mi guión;
Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer
Note la "const" que falta en el primer parámetro. Sin él, un programa (u otro desarrollador) no tiene idea, el primer parámetro debe pasarse "ByVal". Al agregar "const", el archivo .h se documenta a sí mismo para que los desarrolladores que usan otros idiomas puedan escribir fácilmente códigos de trabajo.
Como los parámetros se pasan por valor, no hace ninguna diferencia si especifica const o no desde la perspectiva de la función que llama. Básicamente, no tiene ningún sentido declarar parámetros de paso por valor como const.
Lo que hay que recordar con const es que es mucho más fácil hacer que las cosas sean constantes desde el principio, que intentar ponerlas más tarde.
Use const cuando quiera que algo no cambie, es una sugerencia adicional que describe qué hace su función y qué esperar. He visto muchas API de C que podrían hacer con algunas de ellas, ¡especialmente las que aceptan cadenas en C!
Estaría más inclinado a omitir la palabra clave const en el archivo cpp que en el encabezado, pero como tiendo a cortarlos y pegarlos, se mantendrá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 tu palabra clave const en ambos archivos.
Realmente no hay razón para hacer un parámetro-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 modificarla; o mejor dicho, el compilador se quejará si intentas modificarlo de la manera convencional. Evita que se modifique accidentalmente.
Sé que la pregunta está "un poco" desactualizada, pero cuando la encontré, alguien más también puede hacerlo en el futuro ... ... aún dudo que el pobre amigo se incluya aquí para leer mi comentario :)
Me parece que todavía estamos demasiado limitados a la forma de pensar de estilo C En el paradigma OOP jugamos con objetos, no con tipos. Const objeto puede ser conceptualmente diferente de un objeto no const, específicamente en el sentido de lógico-const (en contraste con bitwise-const). Por lo tanto, incluso si la corrección de la función params 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 trabaja 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. Bien a la derecha Solo dando una imagen diferente de lo que pude ver en otro lugar ...
El parámetro Const es útil solo cuando el parámetro se pasa por referencia, es decir, 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 por valor como constante? :-)
Todos los consts en tus ejemplos no tienen ningún propósito. C ++ es el valor predeterminado de paso por valor, 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 consts adicionales porque
- Son redudantes
- Ellos desordenan el texto
- Me impiden cambiar el valor pasado en los casos en que pueda ser útil o eficiente.