Menu
Coddy logo textTech

RAII como patrón

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 102 de 104.

RAII (Resource Acquisition Is Initialization) es más que un simple modismo de C++: es un potente patrón de diseño que vincula la gestión de recursos al ciclo de vida del objeto. Ya has visto RAII con punteros inteligentes, pero el patrón se aplica a cualquier recurso: descriptores de archivos, conexiones de red, mutex o transacciones de bases de datos.

La idea central es simple: adquirir recursos en el constructor, liberarlos en el destructor. Dado que C++ garantiza que los destructores se ejecuten cuando los objetos salen del alcance, la limpieza ocurre automáticamente, incluso cuando ocurren excepciones:

#include <iostream>
#include <fstream>

class FileGuard {
    std::ofstream file;
public:
    FileGuard(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            std::cout << "Failed to open file\n";
        }
    }
    
    void write(const std::string& text) {
        if (file.is_open()) file << text;
    }
    
    ~FileGuard() {
        if (file.is_open()) {
            file.close();
            std::cout << "File closed automatically\n";
        }
    }
};

int main() {
    {
        FileGuard guard("output.txt");
        guard.write("Hello RAII");
    }  // El destructor se llama aquí - archivo cerrado
    
    std::cout << "After scope\n";
}

RAII destaca al gestionar bloqueos en código multihilo. El std::lock_guard de la biblioteca estándar sigue este patrón: adquiere un mutex en la construcción y lo libera en la destrucción, evitando bloqueos mutuos (deadlocks) por desbloqueos olvidados.

Al implementar clases RAII, recuerde eliminar o implementar adecuadamente las operaciones de copia/movimiento (Regla de los Cinco) para evitar problemas de duplicación de recursos o de doble liberación. RAII transforma la gestión manual de recursos, propensa a errores, en una limpieza automática y segura.

challenge icon

Desafío

Fácil

Vamos a construir un Connection Pool Manager (Gestor de Pool de Conexiones) utilizando RAII para gestionar de forma segura las conexiones a bases de datos. En las aplicaciones reales, las conexiones a bases de datos son recursos costosos que deben adquirirse y liberarse adecuadamente. Crearás un envoltorio (wrapper) RAII que garantice que las conexiones siempre se devuelvan al pool, incluso si ocurren excepciones o si las rutas del código se vuelven complicadas.

Organizarás tu código en tres archivos:

  • ConnectionPool.h: Crea un pool de conexiones simple que gestione un número limitado de conexiones.

    Tu clase ConnectionPool debe rastrear cuántas conexiones están disponibles (comienza con una capacidad pasada al constructor). Implementa:

    • acquire() — si hay una conexión disponible, disminuye el contador e imprime Connection acquired (X available) donde X es el contador restante; devuelve true si tiene éxito, false si no hay conexiones disponibles
    • release() — aumenta el contador de disponibles e imprime Connection released (X available)
    • available() — devuelve el número actual de conexiones disponibles
  • ConnectionGuard.h: Construye el envoltorio RAII que gestiona de forma segura una única conexión.

    Tu clase ConnectionGuard encarna el patrón RAII. Debe:

    • Recibir una referencia a un ConnectionPool en su constructor e intentar adquirir una conexión
    • Almacenar si la adquisición fue exitosa
    • Proporcionar un método isConnected() para verificar si el guard mantiene una conexión válida
    • Liberar automáticamente la conexión de vuelta al pool en el destructor (solo si se adquirió una)
    • Eliminar el constructor de copia y la asignación por copia para evitar la duplicación de recursos (consideración de la Regla de los Cinco)

    Cuando se ejecute el destructor, si se mantenía una conexión, imprime Guard releasing connection antes de llamar a release en el pool.

  • main.cpp: Demuestra la limpieza automática de RAII a través de los ámbitos (scopes).

    Lee dos entradas:

    1. Capacidad del pool (entero)
    2. Número de conexiones a solicitar (entero)

    Crea un ConnectionPool con la capacidad dada. Luego, dentro de un ámbito anidado (usando llaves), crea el número solicitado de objetos ConnectionGuard almacenados en un vector. Para cada guard, imprime si se conectó con éxito:

    • Si está conectado: Guard N: Connected
    • Si no está conectado: Guard N: Failed to connect

    (donde N comienza en 1)

    Después de que termine el ámbito (los guards se destruyen), imprime After scope: X connections available mostrando el estado final del pool.

Por ejemplo, con las entradas 2 y 3:

Connection acquired (1 available)
Guard 1: Connected
Connection acquired (0 available)
Guard 2: Connected
Guard 3: Failed to connect
Guard releasing connection
Connection released (1 available)
Guard releasing connection
Connection released (2 available)
After scope: 2 connections available

Con las entradas 3 y 2:

Connection acquired (2 available)
Guard 1: Connected
Connection acquired (1 available)
Guard 2: Connected
Guard releasing connection
Connection released (2 available)
Guard releasing connection
Connection released (3 available)
After scope: 3 connections available

Observa cómo las conexiones se liberan automáticamente cuando los guards salen de su ámbito; nunca llamas explícitamente a release en tu código principal. Los destructores se ejecutan en orden inverso a la construcción (el último guard se destruye primero), y se garantiza que cada conexión adquirida sea devuelta. Este es el poder de RAII: la limpieza de recursos ocurre de manera automática y confiable, sin importar cómo se salga del ámbito.

Hoja de referencia

RAII (Resource Acquisition Is Initialization) es un patrón de diseño que vincula la gestión de recursos al ciclo de vida del objeto. Los recursos se adquieren en el constructor y se liberan en el destructor.

Dado que C++ garantiza que los destructores se ejecuten cuando los objetos salen del ámbito, la limpieza ocurre automáticamente, incluso cuando ocurren excepciones:

class FileGuard {
    std::ofstream file;
public:
    FileGuard(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            std::cout << "Failed to open file\n";
        }
    }
    
    void write(const std::string& text) {
        if (file.is_open()) file << text;
    }
    
    ~FileGuard() {
        if (file.is_open()) {
            file.close();
            std::cout << "File closed automatically\n";
        }
    }
};

int main() {
    {
        FileGuard guard("output.txt");
        guard.write("Hello RAII");
    }  // El destructor se llama aquí - el archivo se cierra automáticamente
    
    std::cout << "After scope\n";
}

RAII es particularmente útil para gestionar bloqueos en código multihilo. El std::lock_guard de la biblioteca estándar adquiere un mutex al construirse y lo libera al destruirse, evitando bloqueos mutuos (deadlocks) por desbloqueos olvidados.

Al implementar clases RAII, elimine o implemente adecuadamente las operaciones de copia/movimiento (Regla de los Cinco) para evitar problemas de duplicación de recursos o de doble liberación.

Pruébalo tú mismo

#include <iostream>
#include <vector>
#include "ConnectionPool.h"
#include "ConnectionGuard.h"

using namespace std;

int main() {
    // Leer entradas
    int capacity;
    int numConnections;
    cin >> capacity;
    cin >> numConnections;
    
    // TODO: Crear un ConnectionPool con la capacidad dada
    
    // TODO: Crear un ámbito anidado usando llaves
    {
        // TODO: Crear un vector para almacenar objetos ConnectionGuard
        // Sugerencia: Necesitarás usar punteros o punteros inteligentes ya que ConnectionGuard
        // tiene el constructor de copia eliminado
        
        // TODO: Bucle para crear numConnections guards
        // Para cada guard, imprimir ya sea:
        // "Guard N: Connected" o "Guard N: Failed to connect"
        // donde N comienza en 1
        
    }
    // Los guards se destruyen aquí cuando termina el ámbito
    
    // TODO: Imprimir "After scope: X connections available"
    
    return 0;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos