Pregunta

¿Hasta dónde llegas con const?¿Solo haces funciones? const cuando es necesario o lo haces todo y lo usas 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 eso const realmente útil?Personalmente opto por usarlo extensamente, incluidos los parámetros, pero en este caso me pregunto si vale la pena.

También me sorprendió saber que puedes omitir const de los parámetros en una declaración de función, pero puede incluirlos 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.

¿Fue útil?

Solución

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.Probablemente sea de mal estilo hacer esto mucho.

Personalmente tiendo a no usar 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.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 lo suyo, siempre y cuando se mantenga rigurosamente la corrección constante para los tipos de referencia.

Otros consejos

"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ó antes, podría ayudar al compilador a optimizar las cosas un poco (aunque es una posibilidad remota).

A veces (¡demasiado a menudo!) tengo que desenredar el código C++ de otra persona.Y todos sabemos que de alguien más El código C++ es un completo desastre casi por definición :) Así que lo primero que hago para descifrar el flujo de datos local es poner constante 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 constante por defecto y mudable fue requerido para variables no constantes :)

Las dos líneas siguientes son funcionalmente equivalentes:

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

Obviamente no podrás modificar a en el cuerpo de foo si se define de la segunda manera, pero no hay diferencia desde el exterior.

Dónde const Lo que realmente resulta útil es con 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.

Las const extra superfluas son malas desde el punto de vista de la API:

Poner constantes extra superfluas en su código para parámetros de tipo intrínseco pasados ​​por valor satura tu API sin hacer 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 todos lados 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 ¡PELIGROSO!

¿Por qué?

Una rápida lectura errónea del primer parámetro. char * const buffer Podría hacerte 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 a la 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
class 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:

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 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.

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

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

Cuando me ganaba la vida codificando C++, hice todo lo que pude.Usar const es una excelente manera de ayudar al compilador a ayudarlo.Por ejemplo, mantener constantes los valores de retorno de su método puede evitar 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 estará feliz de permitirle asignar un valor al temporal anónimo devuelto por la llamada a la función.Haciéndolo constante:

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

Elimina esta posibilidad.

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

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

Yo digo que conste 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 numérico fuera constante, el compilador se detendría y nos advertiría del error.

Utilizo const en parámetros de función que son referencias (o punteros) que son solo datos [en] y la función no los modificará.Es decir, cuando el propósito de utilizar 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 impone una restricción a 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

Si usas 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 const entre Bar * y p, el compilador me habría dicho eso.

Ah, uno difícil.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.

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

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

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

Para mí, el valor está en 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 cambió uno o ambos.

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

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

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 (this) puede, y probablemente debería, declararse constante.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:

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

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

Puede que este 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 modificar accidentalmente nuestras variables dentro de las funciones (que se supone que no debemos hacer/solo lectura).entonces, si lo hicimos accidentalmente en el momento de la compilación, el compilador nos lo hará saber.Esto es especialmente importante si no eres el único que está trabajando en este proyecto.

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

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

No uso const para parámetros 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 muchos const_cast<> o podría descubrir que marcar un método como constante requiere cambiar una gran cantidad de código porque llama a otros métodos que deberían haberse marcado como constante.

También tiendo a marcar las variables locales constantes 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".

Tiendo a usar const siempre que sea posible.(U otra palabra clave apropiada para el idioma de destino). Hago esto simplemente porque permite al compilador realizar optimizaciones adicionales que de otra manera 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 modifica de todos modos, por lo que puedo pasar por referencia y guardar algunos ciclos de reloj". No creo que alguna vez haga tal cosa, ya que cambia la firma de la función, pero hace el punto.Tal vez haga alguna manipulación diferente de la pila o algo así...El punto es que no lo sé, pero sí sé que intentar ser más inteligente que el compilador sólo 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.

Sobre optimizaciones del compilador: http://www.gotw.ca/gotw/081.htm

Yo uso const donde puedo.Const para los 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 debe cambiar los miembros de la clase.

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, lo que 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 se puede hacer que la referencia en sí sea constante, no es que importe mucho ya que no se puede cambiar el árbitro) .Parece una buena idea proteger todo lo que puedas como constante.Puede omitirlo sin temor a cometer un error si los parámetros son solo POD (incluidos los tipos integrados) y no hay posibilidad de que cambien más a lo largo del camino (p. ej.en su ejemplo el parámetro bool).

No conocía la diferencia en la declaración del archivo .h/.cpp, pero tiene cierto sentido.A 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 la declara como constante (aparte de las optimizaciones).Sin embargo, le ayuda a indicarle al compilador que no cambiará el valor de la variable dentro de la implementación de la función (.ccp).Puede resultar útil en el caso de que esté heredando de una interfaz que permite cambios, pero no necesita cambiar el parámetro para lograr la funcionalidad requerida.

Para resumir:

  • "Normalmente, constante valor de pases por constante es inseguro y engañoso en el mejor de los casos". De GOTW006
  • Pero puedes agregarlos en el .cpp como lo harías con las variables.
  • Tenga en cuenta que la biblioteca estándar no usa const.P.ej. std::vector::at(size_type pos).Lo que es suficientemente bueno para la biblioteca estándar es bueno para mí.

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

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.

Utilice const cuando desee que algo no cambie: es una sugerencia adicional que describe lo que hace su función y qué esperar.He visto muchas API de C a las que les vendría bien algunas de ellas, ¡especialmente aquellas que aceptan cadenas C!

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

Realmente no hay razón para hacer que un parámetro de valor sea "constante", 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ás pasando algo más grande (p. ej.una estructura con muchos miembros) por referencia, en cuyo caso asegura que la función no pueda modificarla;o mejor dicho, el compilador se quejará si intentas modificarlo de la forma convencional.Evita que se modifique accidentalmente.

El parámetro constante es útil solo cuando el parámetro se pasa por referencia, es decir, por referencia o por puntero.Cuando el compilador ve un parámetro constante, 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 que un parámetro por valor sea constante?:-)

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

Todas las constantes en sus ejemplos no tienen ningún propósito.C++ pasa por valor de forma predeterminada, por lo que la función obtiene copias de esos enteros y booleanos.Incluso si la función los modifica, la copia de la persona que llama no se ve afectada.

Entonces evitaría constantes adicionales porque

  • son redundantes
  • Ellos desordenan el texto
  • Me impiden cambiar el valor aprobado en los casos en que podría ser útil o eficiente.

Sé que la pregunta está "un poco" desactualizada, pero cuando la encontré, es posible que alguien más también la haga en el futuro......todavía dudo que el pobrecito se incluya aquí para leer mi comentario :)

Me parece que todavía estamos demasiado confinados al modo de pensar del estilo C.En el paradigma de la programación orientada a objetos jugamos con objetos, no con tipos.Un objeto constante puede ser conceptualmente diferente de un objeto no constante, específicamente en el sentido de constante lógica (en contraste con constante bit a bit).Por lo tanto, incluso si la corrección constante de los parámetros de 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 constante, 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;

}

PD.:puede argumentar que la referencia (constante) sería más apropiada aquí y le dará el mismo comportamiento.Bueno, claro.Simplemente dando una imagen diferente de lo que pude ver en otros lugares...

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 usando ByRef o ByVal.

Por supuesto, el programa te lo indica generando un error de excepción en la línea donde cometiste el error, pero luego debes adivinar cuál de los 2 a 10 parámetros es incorrecto.

Así que ahora tengo la desagradable tarea de intentar convencer a un desarrollador de que realmente debería definir sus variables (en el archivo .h) de una manera que permita un método automatizado para crear fácilmente todas las definiciones de funciones de VB.NET.Luego dirán con aire de suficiencia: "lee el...documentación."

He escrito un script awk que analiza un archivo .h y crea todos los comandos de Declarar función, pero sin un indicador de qué variables son R/O vs R/W, solo hace la mitad del trabajo.

EDITAR:

Alentado por otro usuario, agrego lo siguiente;

A continuación se muestra un ejemplo de una entrada .h (en mi opinión) mal formada;

typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );

El VB resultante de mi script;

    Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer

Tenga en cuenta la "constancia" que falta en el primer parámetro.Sin él, un programa (u otro desarrollador) no tiene idea de que el primer parámetro debe aprobarse "ByVal". Al agregar el "const", el archivo .h se documenta a sí mismo para que los desarrolladores que usan otros idiomas puedan escribir fácilmente el código de trabajo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top