El modificador static
La palabra clave static altera el tiempo de vida y la visibilidad de las variables y funciones dependiendo de su ubicación.
Fueera de clases y estructuras
Cuando se emplea fuera de una clase, static limita el enlace del identificador al archivo donde se declara (enlace interno). Esto significa que la variable o función no será visible para otros archivos durante el proceso de vinculación.
Dentro de clases y estructuras
Los miembros estáticos pertenecen a la clase en sí, no a una instancia específica.
- Variables estáticas: Existe una única copia del dato para todas las instancias. Se debe definir fuera del cuerpo de la clase para asignar la memoria necesaria.
- Métodos estáticos: Pueden ser invocados sin crear un objeto. Solo pueden acceder a otros miembros estáticos, ya que carecen del puntero implícito
this.
struct GestorDatos {
static int contadorGlobal;
static void Incrementar() {
contadorGlobal++;
}
};
// Definición externa obligatoria
int GestorDatos::contadorGlobal = 0;
Static local
Una variable declarada como static dentro de una función mantiene su valor entre sucesivas llamadas. Se inicializa una sola vez y su ciclo de vida se extiende hasta que finaliza la ejecución del programa, aunque su visibilidad sigue restringida al bloque local.
El calificador const
C++ utiliza const para garantizar la inmutabilidad de los datos. Su comportamiento varía significativamente según la posición en la declaración.
Punteros y Constancia
- Puntero a constante (
const T*): El valor apuntado no puede modificarse, pero el puntero puede cambiar de dirección. - Puntero constante (
T* const): La dirección de memoria almacenada es fija, pero el valor al que apunta puede alterarse. - Puntero constante a contenido constante (
const T* const): Ni la dirección ni el valor pueden ser modificados.
int valorBase = 50;
const int* ptrLectura = &valorBase; // No puedo hacer *ptrLectura = 20;
int* const ptrFijo = &valorBase; // Puedo hacer *ptrFijo = 20, pero no ptrFijo = nullptr;
Funciones miembro constantes
Un método marcado con const al final de su firma garantiza que no modificará el estado del objeto (sus atributos). Los objetos declarados como constantes solo pueden invocar métodos que tengan esta calificación.
Uso de mutable
La palabra clave mutable permite que un atributo de clase sea modificado incluso dentro de métodos const. Es útil para implementar caches o mecanismos de sincronización (como mutexes) que no afectan el estado lógico del objeto.
class CacheSistema {
private:
mutable int accesos;
int datos;
public:
int LeerDatos() const {
accesos++; // Permitido por ser mutable
return datos;
}
};
Gestión de memoria: new y delete
A diferencia de malloc, el operador new no solo reserva memoria en el heap, sino que también invoca el constructor del tipo de objeto especificado. Por su parte, delete ejecuta el destructor antes de liberar la memoria.
- Arreglos: Se debe usar
new[]ydelete[]en pareja para asegurar que se llamen los destructores de cada elemento. - Placement new: Permite construir un objeto en una dirección de memoria ya reservada. Es común en sistemas de bajo nivel o asignadores personalizados.
void* buffer = operator new(sizeof(MiClase));
MiClase* objeto = new(buffer) MiClase(); // Construcción en sitio
// Para liberar:
objeto->~MiClase();
operator delete(buffer);
Especificadores explícitos y automáticos
explicit
Se aplica a constructores para evitar que el compilador realice conversiones de tipo implícitas. Esto previene errores lógicos donde un valor se transforma automáticamente en un objeto de forma inesperada.
auto
Permite que el compilador deduzca el tipo de una variable a partir de su inicializador. Es especialmente útil con iteradores complejos o tipos de plantillas largas.
constexpr
Indica que el valor de una expresión o el retorno de una función puede ser evaluado en tiempo de compilación. Esto mejora el rendimiento al desplazar cálculos del tiempo de ejecución al tiempo de compilación.
Gestión de excepciones con try, throw y catch
El manejo de errores en C++ se basa en la transferencia de control desde el punto donde ocurre un problema (throw) hacia un bloque de código diseñado para resolverlo (catch).
try {
if (divisor == 0) {
throw std::runtime_error("División por cero detectada");
}
double resultado = dividendo / divisor;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Error desconocido capturado" << std::endl;
}
Jerarquía estándar de excepciones
C++ define una jerarquía de clases de excepción bajo <exception>:
| Excepción | Descripción |
|---|---|
| std::bad_alloc | Lanzada cuando falla la asignación de memoria con new. |
| std::out_of_range | Acceso a elementos fuera de los límites (ej. en std::vector::at). |
| std::logic_error | Errores que podrían haberse evitado mediante una lógica de programación correcta. |
| std::runtime_error | Problemas detectables solo durante la ejecución. |