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: identificadores de archivos, conexiones de red, mutexes o transacciones de bases de datos.
La idea central es sencilla: adquiere los recursos en el constructor y libéralos en el destructor. Como C++ garantiza que los destructores se ejecuten cuando los objetos salen del ámbito, la limpieza se realiza 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 desbloquear.
Al implementar clases RAII, recuerda eliminar o implementar correctamente las operaciones de copia/movimiento (Regla de Cinco) para evitar la duplicación de recursos o problemas de doble liberación. RAII transforma la gestión manual de recursos propensa a errores en una limpieza segura y automática.
Desafío
FácilConstruyamos un Administrador de grupos de conexiones 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 clase
ConnectionPooldebe llevar el control de cuántas conexiones están disponibles (comenzando con una capacidad pasada al constructor). Implementa:acquire(): si hay una conexión disponible, disminuye el contador e imprimeConnection acquired (X available), donde X es el contador restante; devuelvetruesi tiene éxito yfalsesi no hay conexiones disponiblesrelease(): aumenta el contador de conexiones disponibles e imprimeConnection released (X available)available(): devuelve el número actual de conexiones disponibles
ConnectionGuard.h: Construye el envoltorio RAII que gestione de forma segura una sola conexión.Tu clase
ConnectionGuardrepresenta el patrón RAII. Debe:- Recibir una referencia a un
ConnectionPoolen su constructor e intentar adquirir una conexión - Almacenar si la adquisición tuvo éxito
- Proporcionar un método
isConnected()para comprobar si el guardián contiene una conexión válida - Liberar automáticamente la conexión de vuelta al grupo en el destructor (solo si se adquirió una)
- Eliminar el constructor de copia y la asignación de copia para evitar la duplicación de recursos (consideración de la Regla de Cinco)
Cuando se ejecute el destructor, si había una conexión retenida, imprime
Guard releasing connectionantes de llamar a release en el grupo.- Recibir una referencia a un
main.cpp: Demuestra la limpieza automática de RAII mediante ámbitos.Lee dos entradas:
- Capacidad del grupo (entero)
- Número de conexiones que solicitar (entero)
Crea un
ConnectionPoolcon la capacidad indicada. Después, dentro de un ámbito anidado (usando llaves), crea el número solicitado de objetosConnectionGuardalmacenados en un vector. Para cada guardián, imprime si se conectó correctamente:- Si se conectó:
Guard N: Connected - Si no se conectó:
Guard N: Failed to connect
(donde N comienza en 1)
Cuando termina el ámbito (se destruyen los guardianes), 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 availableCon 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 availableObserva cómo las conexiones se liberan automáticamente cuando los guardianes salen de su ámbito. Nunca llamas explícitamente a release en tu código principal. Los destructores se ejecutan en orden inverso al de construcción (el último guardián se destruye primero), 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
// Pista: 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 el ámbito termina
// TODO: Print "After scope: X connections available"
return 0;
}
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
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online