¿Cuántos y cuáles son los usos de "const" en C ++?

129

Como programador novato de C ++, hay algunas construcciones que todavía me parecen muy oscuras, una de ellas es const. Puede usarlo en tantos lugares y con tantos efectos diferentes que es casi imposible que un principiante salga vivo. ¿Algún gurú de C ++ explicará una vez para siempre los diversos usos y si y / o por qué no usarlos?

Tunnuz
fuente
buscando exactamente esa pregunta: D
alamin

Respuestas:

100

Intentando recopilar algunos usos:

Enlazar algunos temporales a referencia a constante, para alargar su vida útil. La referencia puede ser una base, y su destructor no necesita ser virtual, el destructor correcto todavía se llama:

ScopeGuard const& guard = MakeGuard(&cleanUpFunction);

Explicación , usando el código:

struct ScopeGuard { 
    ~ScopeGuard() { } // not virtual
};

template<typename T> struct Derived : ScopeGuard { 
    T t; 
    Derived(T t):t(t) { }
    ~Derived() {
        t(); // call function
    }
};

template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }

Este truco se usa en la clase de utilidad ScopeGuard de Alexandrescu. Una vez que el temporal sale del alcance, el destructor de Derived se llama correctamente. El código anterior pierde algunos pequeños detalles, pero ese es el gran problema.


Use const para decirle a otros que los métodos no cambiarán el estado lógico de este objeto.

struct SmartPtr {
    int getCopies() const { return mCopiesMade; }
};

Use const para las clases de copiar y escribir , para que el compilador lo ayude a decidir cuándo y cuándo no necesita copiar.

struct MyString {
    char * getData() { /* copy: caller might write */ return mData; }
    char const* getData() const { return mData; }
};

Explicación : es posible que desee compartir datos cuando copie algo, siempre y cuando los datos del objeto original y el copiado sigan siendo los mismos. Una vez que uno de los objetos cambia los datos, ahora necesita dos versiones: una para el original y otra para la copia. Es decir, copia en una escritura a cualquiera de los objetos, de modo que ahora ambos tengan su propia versión.

Usando código :

int main() {
    string const a = "1234";
    string const b = a;
    // outputs the same address for COW strings
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

El fragmento anterior imprime la misma dirección en mi GCC, porque la biblioteca C ++ utilizada implementa una copia en escritura std::string. Ambas cadenas, aunque son objetos distintos, comparten la misma memoria para sus datos de cadena. Hacer bno const preferirá la versión sin const del operator[]GCC y creará una copia del búfer de memoria de respaldo, porque podríamos cambiarlo y no debe afectar los datos de a!

int main() {
    string const a = "1234";
    string b = a;
    // outputs different addresses!
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Para que el constructor de copias haga copias de objetos constantes y temporales :

struct MyClass {
    MyClass(MyClass const& that) { /* make copy of that */ }
};

Para hacer constantes que trivialmente no pueden cambiar

double const PI = 3.1415;

Para pasar objetos arbitrarios por referencia en lugar de por valor , para evitar el paso por valor posiblemente costoso o imposible

void PrintIt(Object const& obj) {
    // ...
}
Johannes Schaub - litb
fuente
2
¿Puede explicar por favor el primer y el tercer uso en sus ejemplos?
tunnuz
"Para garantizar a la persona que llama que el parámetro no puede ser NULL" No veo cómo const tiene algo que ver con ese ejemplo.
Logan Capaldo
Uy, así que fallo. De alguna manera comencé a escribir sobre referencias. muchas gracias por gemir :) por supuesto, eliminaré esas cosas ahora :)
Johannes Schaub - litb
3
Por favor explique el primer ejemplo. No tiene mucho sentido para mí.
chikuba
28

Realmente hay 2 usos principales de const en C ++.

Valores constantes

Si un valor tiene la forma de una variable, miembro o parámetro que no se alterará (o no debería) durante su vida útil, debe marcarlo como constante. Esto ayuda a prevenir mutaciones en el objeto. Por ejemplo, en la siguiente función no necesito cambiar la instancia de Estudiante aprobada, así que la marco const.

void PrintStudent(const Student& student) {
  cout << student.GetName();
}

En cuanto a por qué harías esto. Es mucho más fácil razonar sobre un algoritmo si sabe que los datos subyacentes no pueden cambiar. "const" ayuda, pero no garantiza que esto se logrará.

Obviamente, imprimir datos en Cout no requiere mucho pensamiento :)

Marcar un método de miembro como constante

En el ejemplo anterior, marqué Student como const. Pero, ¿cómo sabía C ++ que llamar al método GetName () en student no mutaría el objeto? La respuesta es que el método fue marcado como constante.

class Student {
  public:
    string GetName() const { ... }
};

Marcar un método "const" hace 2 cosas. Principalmente le dice a C ++ que este método no mutará mi objeto. Lo segundo es que todas las variables miembro ahora se tratarán como si estuvieran marcadas como const. Esto ayuda, pero no le impide modificar la instancia de su clase.

Este es un ejemplo extremadamente simple, pero espero que ayude a responder sus preguntas.

JaredPar
fuente
16

Tenga cuidado de comprender la diferencia entre estas 4 declaraciones:

Las siguientes 2 declaraciones son idénticas semánticamente. Puede cambiar dónde apuntan ccp1 y ccp2, pero no puede cambiar la cosa a la que apuntan.

const char* ccp1;
char const* ccp2;

A continuación, el puntero es constante, por lo que para ser significativo debe inicializarse para señalar algo. No puede hacer que señale a otra cosa, sin embargo, lo que señala puede cambiarse.

char* const cpc = &something_possibly_not_const;

Finalmente, combinamos los dos, por lo que la cosa a la que apunta no puede modificarse y el puntero no puede apuntar a ningún otro lado.

const char* const ccpc = &const_obj;

La regla espiral en sentido horario puede ayudar a desenredar una declaración http://c-faq.com/decl/spiral.anderson.html

Steve Folly
fuente
De una manera indirecta, sí. La regla espiral en el sentido de las agujas del reloj lo describe mejor: comience por el nombre (kpPointer) y dibuje una espiral en el sentido de las agujas del reloj que salga del token y diga cada token. Obviamente, no hay nada a la derecha de kpPointer pero aún funciona.
Steve Folly el
3

Como una pequeña nota, mientras leo aquí , es útil notar que

const se aplica a lo que esté a su izquierda inmediata (excepto si no hay nada allí, en cuyo caso se aplica a lo que sea su derecha inmediata).

JoePerkins
fuente