Menu

const y constantes en C++: const, constexpr y consteval

Cómo declarar valores de solo lectura en C++ con const, la diferencia entre const y constexpr, punteros const frente a punteros a const, y funciones miembro const.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Por qué existen las constantes

Una constante es un valor que prometes no cambiar nunca después de asignarlo. Marcar algo como const cumple dos funciones a la vez: documenta tu intención ante cualquiera que lea el código y permite que el compilador imponga esa intención: cualquier línea que intente modificar el valor se convierte en un error de compilación en lugar de un fallo silencioso en tiempo de ejecución.

Mientras que la palabra clave auto deja que el compilador infiera el tipo de una variable, const restringe lo que puedes hacer con esa variable. Las dos se combinan sin problemas: const auto limit = 100; es un int de solo lectura.

Declarar un valor const

Pon const antes del tipo. Una variable const debe inicializarse en la misma línea, porque no hay ningún momento posterior en el que se te permita asignarle un valor.

Descomenta la asignación y el programa no compilará: el compilador informa de "assignment of read-only variable". Ese es justamente el objetivo: el error se detecta antes de que el programa llegue a ejecutarse.

Un hábito común de principiante heredado de C es #define MAX_USERS 100. Evítalo. Una macro es una sustitución de texto ciega, sin tipo y sin respetar el ámbito, por lo que no puede inspeccionarse en un depurador y produce mensajes de error confusos. Una variable const (o constexpr) tiene comprobación de tipos y ámbito como cualquier otra.

const frente a constexpr

Ambas palabras clave te dan un valor que no puede cambiar, pero responden a preguntas distintas. const dice "esto no cambia nunca después de asignarse". constexpr dice algo más fuerte: "esto puede calcularse en tiempo de compilación", y todo lo constexpr es automáticamente const también.

La regla práctica: recurre a constexpr siempre que el valor sea un literal fijo o un cálculo que el compilador pueda hacer (tamaños de arrays, longitudes de búferes, etiquetas de switch, argumentos de plantillas). Usa const a secas cuando el valor se decide en tiempo de ejecución pero no debe cambiar después, como una copia const de un argumento de función.

Desde C++20 existe también consteval, usado en funciones que deben ejecutarse en tiempo de compilación:

consteval int square(int x) { return x * x; }
constexpr int area = square(8); // calculado durante la compilación

Una función constexpr puede ejecutarse en tiempo de compilación; una función consteval siempre debe hacerlo, o de lo contrario es un error.

Punteros y const: lee de derecha a izquierda

Aquí es donde const confunde a la gente, porque la palabra clave puede ir a cualquier lado del * y los dos significados son opuestos. El truco está en leer la declaración de derecha a izquierda.

Lee int* const p2 de derecha a izquierda: "p2 es un puntero const a int". Lee const int* p1 como "p1 es un puntero a const int". Si te equivocas aquí, perderás un buen rato desconcertado por un error que dice que no puedes modificar algo que creías mutable.

Un detalle práctico: nunca tomes la dirección de un const y elimines el const con un cast para modificar el objeto subyacente. Hacerlo es comportamiento indefinido si el objeto original era realmente const, y el compilador puede asumir que el valor nunca cambia, por lo que tu "escritura" puede simplemente ignorarse.

Referencias const como parámetros de función

El uso cotidiano más común de const es pasar objetos grandes por referencia sin copiarlos. Un parámetro const& evita la copia y promete que la función no modificará el argumento del llamador.

Pasar por const& es la opción por defecto para cualquier parámetro mayor que un par de bytes (strings, vectores, tus propias clases). Ten en cuenta que también permite que la función acepte un temporal como "Grace": una referencia no const a secas no puede enlazarse a un temporal, así que quitar el const aquí rechazaría esa segunda llamada.

Funciones miembro const

Cuando escribes una clase, marca cualquier método que no modifique el objeto con un const al final. Esto es lo que hace que el método sea invocable en instancias const y en parámetros const&: sin él, no puedes leer tu propio objeto a través de una referencia const.

La disciplina de marcar los métodos de solo lectura como const se llama const correctness. Hazlo bien desde el principio: un método que debería haber sido const es fácil de añadir, pero adaptar const a posteriori en una base de código grande es doloroso, porque cada llamador a través de una referencia const depende de ello.

Siguiente: operadores

Ahora que tus valores pueden bloquearse con const, el siguiente paso es hacer cosas con ellos. La página de operadores cubre los operadores aritméticos, de comparación, lógicos y de asignación, incluidos los detalles peliagudos sobre la división entera, la precedencia de operadores y cómo interactúa const con los operadores de asignación que no tienes permitido usar.

Preguntas frecuentes

¿Cuál es la diferencia entre const y constexpr en C++?

const significa que el valor no puede cambiar tras la inicialización, pero puede calcularse en tiempo de ejecución. constexpr es más estricto: garantiza que el valor puede calcularse en tiempo de compilación, por lo que puede usarse donde se requiere una constante en tiempo de compilación (tamaños de arrays, argumentos de plantillas, etiquetas de switch). Todo objeto constexpr es también const, pero no todo objeto const es constexpr.

¿Cómo declaro una constante en C++?

Pon const antes del tipo y asígnale un valor: const int maxUsers = 100;. Una variable const debe inicializarse al declararla, porque nunca podrás asignarle un valor después. Para constantes en tiempo de compilación, prefiere constexpr int maxUsers = 100;. Evita la antigua macro #define al estilo de C: no tiene tipo e ignora el ámbito.

¿Qué significa un puntero const en C++?

Depende de dónde se coloque const. const int* p es un puntero a const: puedes reapuntar p, pero no puedes cambiar *p. int* const p es un puntero const: puedes cambiar *p, pero no puedes reapuntar p. Lee la declaración de derecha a izquierda: int* const es "puntero const a int".

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR