Capítulo 11: Auto, Const, Constexpr y Static
C++ moderno incluye características poderosas para escribir código más seguro, eficiente y expresivo. En este capítulo exploraremos la deducción automática de tipos, constantes en tiempo de compilación, y el comportamiento especial de las variables estáticas.
Objetivos
- Dominar la deducción de tipos con auto
- Entender la diferencia entre const y constexpr
- Usar variables estáticas locales correctamente
- Aplicar estos conceptos para escribir código más robusto
Deducción de tipos con auto
La palabra clave auto permite que el compilador deduzca automáticamente el tipo
de una variable basándose en su inicializador. Esto hace el código más limpio y mantenible.
// Antes de auto
std::vector<std::string>::iterator it = myVector.begin();
// Con auto - mucho más limpio
auto it = myVector.begin();
// auto deduce el tipo del resultado
auto sum = 10 + 20.5; // double (promoción automática)
auto value = 42; // int
auto name = "Claude"; // const char*
auto pi = 3.14f; // float AAA: Almost Always Auto
Muchos expertos en C++ recomiendan el principio AAA (Almost Always Auto) . Esto sigue el principio DRY (Don't Repeat Yourself) y hace el código más resistente a cambios.
// Sin auto - repetitivo y propenso a errores
std::map<std::string, std::vector<int>> myMap;
std::map<std::string, std::vector<int>>::iterator it = myMap.begin();
// Con auto - limpio y mantenible
auto myMap = std::map<std::string, std::vector<int>>{};
auto it = myMap.begin();
// Uniform initialization con auto
auto x = int{42}; // Inicialización uniforme
auto y = int{}; // Valor por defecto (0)
auto vec = std::vector<int>{1, 2, 3, 4, 5};
Las conversiones implícitas pueden causar bugs sutiles. Con auto y conversiones
explícitas, el código es más claro sobre las intenciones:
float a = 10.5;
int b = a; // ¿Bug o intencional? No está claro
// Mejor - intención clara
auto c = static_cast<int>(a); // Conversión explícita Const: Inmutabilidad en tiempo de ejecución
const marca variables como de solo lectura después de su inicialización.
Es fundamental para escribir código seguro y expresar intenciones claramente.
const int MAX_USERS = 100; // No puede cambiar
const auto pi = 3.14159; // auto con const
void processUser(const User& user) {
// user es de solo lectura, no se puede modificar
// Pasar por referencia const evita copias innecesarias
}
// const con punteros
const int* ptr1; // Puntero a int constante (el valor no cambia)
int* const ptr2; // Puntero constante a int (el puntero no cambia)
const int* const ptr3; // Todo es constante Constexpr: Computación en tiempo de compilación
constexpr indica que algo puede ser evaluado en tiempo de compilación .
Esto permite optimizaciones significativas y garantías más fuertes que const.
// Función constexpr - puede ejecutarse en tiempo de compilación
constexpr std::uint64_t factorial(const std::uint8_t n) {
std::uint64_t result = 1;
for (std::uint8_t i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
int main() {
// const - valor constante, puede ser runtime
const auto runtime_result = factorial(5);
// constexpr - DEBE ser compile-time
constexpr auto compile_time_result = factorial(5); // 120 calculado al compilar
// Esto permite usarlo en contextos compile-time
std::array<int, factorial(4)> myArray; // Array de tamaño 24
} ¿Cuándo usar const vs constexpr? Intermedio
- const: Para valores que no cambian después de inicialización, pero pueden ser calculados en runtime
- constexpr: Para valores que PUEDEN y DEBERÍAN ser calculados en compile-time
- constexpr implica const: Una variable constexpr es automáticamente const
- C++17 y posterior: constexpr se ha vuelto mucho más poderoso, permitiendo if constexpr y más
¿Por qué constexpr auto result = getUserInput(); no compilaría?
Porque getUserInput() requiere entrada del usuario en tiempo de ejecución, y constexpr requiere que el valor sea conocido en tiempo de compilación. Las variables constexpr solo pueden inicializarse con expresiones que el compilador puede evaluar completamente.
Static: Variables con duración de almacenamiento estático
static tiene varios significados en C++, pero para variables locales significa que
la variable persiste entre llamadas a la función.
void countCalls() {
static int counter = 0; // Se inicializa SOLO la primera vez
++counter;
std::cout << "Llamada número: " << counter << "\n";
}
int main() {
countCalls(); // Imprime: Llamada número: 1
countCalls(); // Imprime: Llamada número: 2
countCalls(); // Imprime: Llamada número: 3
// counter mantiene su valor entre llamadas
}
// Ejemplo práctico: Singleton pattern simplificado
Database& getDatabase() {
static Database db("connection_string"); // Creado una sola vez
return db; // Siempre retorna la misma instancia
}
Desde C++11, la inicialización de variables static locales es thread-safe.
El compilador garantiza que se inicialicen exactamente una vez, incluso con múltiples threads.
Contextos de static
- Variables locales static: Persisten entre llamadas (este capítulo)
- Variables globales static: Linkage interno (solo visible en ese archivo)
- Miembros static de clase: Compartidos por todas las instancias
- Funciones static de clase: No necesitan instancia para llamarse
Combinando conceptos
Estos conceptos se pueden combinar para escribir código más eficiente y expresivo:
// Template con auto, const y constexpr
template<typename T>
constexpr auto max(const T& a, const T& b) {
return (a > b) ? a : b;
}
// Función con cache usando static
auto fibonacci(int n) {
static std::map<int, long long> cache;
if (auto it = cache.find(n); it != cache.end()) {
return it->second; // Ya calculado
}
if (n <= 1) return n;
auto result = fibonacci(n-1) + fibonacci(n-2);
cache[n] = result; // Guardar en cache
return result;
}
// Constexpr if (C++17) para decisiones en compile-time
template<typename T>
auto processValue(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2; // Para enteros
} else {
return value / 2; // Para flotantes
}
} Resumen
autodeduce tipos automáticamente, haciendo el código más mantenibleconstmarca valores como inmutables en runtimeconstexprpermite computación en compile-time para mejor rendimientostaticen variables locales las hace persistir entre llamadas- Estos conceptos son fundamentales en C++ moderno para código eficiente y seguro