Uso y comportamiento de palabras clave esenciales en C++

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[] y delete[] 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.

Etiquetas: cpp memoria gestion-de-errores programación-orientada-objetos

Publicado el 9-20 21:50