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 con el ciclo de vida de los objetos. Ya has visto RAII con punteros inteligentes, pero el patrón se aplica a cualquier recurso: controladores de archivos, conexiones de red, mutexes o transacciones de bases de datos.

La idea central es sencilla: acquire recursos en el constructor y liberarlos en el destructor. Como C++ garantiza que los destructores se ejecuten cuando los objetos salen de ámbito, la limpieza ocurre automáticamente, incluso cuando se producen 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");
    }  // Destructor llamado aquí - archivo cerrado
    
    std::cout << "After scope\n";
}

RAII destaca al gestionar bloqueos en código multihilo. std::lock_guard de la biblioteca estándar sigue este patrón: adquiere un mutex durante la construcción y lo libera durante la destrucción, evitando interbloqueos causados por olvidarse de desbloquearlo.

Al implementar clases RAII, recuerda hacer delete o implementar correctamente las operaciones de copy/move (Rule of Five) para evitar problemas de duplicación de recursos o doble liberación. RAII transforma la gestión manual de recursos propensa a errores en una limpieza segura y automática.

challenge icon

Desafío

Fácil

Construyamos un Connection Pool Manager usando RAII para gestionar de forma segura las conexiones a la base de datos. En las aplicaciones reales, las conexiones a la base de datos son recursos costosos que deben adquirirse y liberarse correctamente. Crearás un envoltorio RAII que garantiza que las conexiones siempre se devuelvan al grupo, incluso si se producen excepciones o las rutas del código se vuelven complicadas.

Organizarás tu código en tres archivos:

  • ConnectionPool.h: Crea un grupo de conexiones sencillo que gestione un número limitado de conexiones.

    Tu ConnectionPool class debe llevar un registro de cuántas conexiones están available (comienza con una capacidad pasada al constructor). Implementa:

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

    Tu ConnectionGuard class representa el patrón RAII. Debe:

    • Recibir una referencia a un ConnectionPool en su constructor e intentar adquirir una conexión
    • Guardar si la adquisición tuvo éxito
    • Proporcionar un método isConnected() para comprobar si el guard mantiene una conexión válida
    • Liberar automatically la conexión de vuelta al grupo en el destructor (solo si se adquirió una)
    • Eliminar el copy constructor y la asignación de copia para evitar la duplicación de recursos (consideración de la Rule of Five)

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

  • main.cpp: Demuestra la limpieza automática de RAII mediante ámbitos.

    Lee dos entradas:

    1. Capacidad del grupo (entero)
    2. Número de conexiones que solicitar (entero)

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

    • 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 (se destruyen los guards), imprime After scope: X connections available, mostrando el estado final del grupo.

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 automatically cuando los guards salen de su ámbito: nunca llamas explícitamente a release en tu código principal. Los destructors se ejecutan en el orden inverso al de construcción (el último guard creado es el primero en destruirse), y se garantiza que cada conexión adquirida se devuelva. Este es el poder de RAII: la limpieza de recursos ocurre de forma automática y fiable, sin importar cómo termine el ámbito.

Pruébalo tú mismo

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

using namespace std;

int main() {
    // Lee las entradas
    int capacity;
    int numConnections;
    cin >> capacity;
    cin >> numConnections;
    
    // TODO: Crea un ConnectionPool con la capacidad dada
    
    // TODO: Crea un ámbito anidado usando llaves
    {
        // TODO: Crea un vector para almacenar objetos ConnectionGuard
        // Hint: 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, imprime ya sea:
        // "Guard N: Connected" or "Guard N: Failed to connect"
        // donde N empieza en 1
        
    }
    // Los guards se destruyen aquí cuando termina el ámbito
    
    // TODO: Print "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

Practica por tu cuenta: Compilador de C++ online