RAII como Padrão
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 102 de 104.
RAII (Resource Acquisition Is Initialization) é mais do que apenas um idioma do C++ — é um poderoso padrão de projeto que vincula o gerenciamento de recursos ao tempo de vida do objeto. Você já viu RAII com ponteiros inteligentes, mas o padrão se aplica a qualquer recurso: identificadores de arquivo, conexões de rede, mutexes ou transações de banco de dados.
A ideia central é simples: adquira os recursos no construtor e libere-os no destrutor. Como o C++ garante que os destrutores sejam executados quando os objetos saem do escopo, a limpeza acontece automaticamente — mesmo quando ocorrem exceções:
#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");
} // Destrutor chamado aqui - arquivo fechado
std::cout << "After scope\n";
}RAII se destaca ao gerenciar bloqueios em código multithread. O std::lock_guard da biblioteca padrão segue este padrão: ele adquire um mutex na construção e o libera na destruição, evitando deadlocks causados por desbloqueios esquecidos.
Ao implementar classes RAII, lembre-se de excluir ou implementar corretamente as operações de cópia/movimentação (Regra dos Cinco) para evitar problemas de duplicação ou liberação dupla de recursos. RAII transforma o gerenciamento manual de recursos, propenso a erros, em uma limpeza segura e automática.
Desafio
FácilVamos criar um Gerenciador de Pool de Conexões usando RAII para gerenciar conexões de banco de dados com segurança. Em aplicações reais, as conexões de banco de dados são recursos dispendiosos que precisam ser adquiridos e liberados adequadamente. Você criará um wrapper RAII que garante que as conexões sejam sempre devolvidas ao pool, mesmo quando exceções ocorrerem ou os caminhos de execução do código se tornarem complicados.
Você organizará seu código em três arquivos:
ConnectionPool.h: crie um pool de conexões simples que gerencie um número limitado de conexões.Sua classe
ConnectionPooldeve controlar quantas conexões estão disponíveis (começando com uma capacidade passada ao construtor). Implemente:acquire(): se uma conexão estiver disponível, diminua a contagem e imprimaConnection acquired (X available), onde X é a contagem restante; retornetruese for bem-sucedido efalsese não houver conexões disponíveisrelease(): aumente a contagem de conexões disponíveis e imprimaConnection released (X available)available(): retorne o número atual de conexões disponíveis
ConnectionGuard.h: crie o wrapper RAII que gerencia uma única conexão com segurança.Sua classe
ConnectionGuardrepresenta o padrão RAII. Ela deve:- Receber uma referência a um
ConnectionPoolem seu construtor e tentar adquirir uma conexão - Armazenar se a aquisição foi bem-sucedida
- Fornecer um método
isConnected()para verificar se o guard mantém uma conexão válida - Liberar automaticamente a conexão de volta ao pool no destrutor (somente se uma conexão tiver sido adquirida)
- Excluir o construtor de cópia e a atribuição por cópia para impedir a duplicação de recursos (consideração da Rule of Five)
Quando o destrutor for executado, se uma conexão estiver sendo mantida, imprima
Guard releasing connectionantes de chamar release no pool.- Receber uma referência a um
main.cpp: demonstre a limpeza automática do RAII por meio de escopos.Leia duas entradas:
- Capacidade do pool (inteiro)
- Número de conexões a solicitar (inteiro)
Crie um
ConnectionPoolcom a capacidade fornecida. Em seguida, dentro de um escopo aninhado (usando chaves), crie o número solicitado de objetosConnectionGuardarmazenados em um vetor. Para cada guard, imprima se ele se conectou com sucesso:- Se conectado:
Guard N: Connected - Se não estiver conectado:
Guard N: Failed to connect
(onde N começa em 1)
Após o fim do escopo (quando os guards forem destruídos), imprima
After scope: X connections available, mostrando o estado final do pool.
Por exemplo, com as entradas 2 e 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 availableCom as entradas 3 e 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 availableObserve como as conexões são liberadas automaticamente quando os guards saem do escopo. Você nunca chama explicitamente release no seu código principal. Os destrutores são executados na ordem inversa da construção (o último guard é destruído primeiro), e toda conexão adquirida tem seu retorno garantido. Esse é o poder do RAII: a limpeza dos recursos acontece de forma automática e confiável, independentemente de como o escopo é encerrado.
Experimente você mesmo
#include <iostream>
#include <vector>
#include "ConnectionPool.h"
#include "ConnectionGuard.h"
using namespace std;
int main() {
// Ler entradas
int capacity;
int numConnections;
cin >> capacity;
cin >> numConnections;
// TODO: Crie um ConnectionPool com a capacidade fornecida
// TODO: Crie um escopo aninhado usando chaves
{
// TODO: Crie um vector para armazenar objetos ConnectionGuard
// Dica: Você precisará usar ponteiros ou smart pointers já que ConnectionGuard
// tem o construtor de cópia deletado
// TODO: Faça um loop para criar numConnections guards
// Para cada guard, imprima:
// "Guard N: Connected" or "Guard N: Failed to connect"
// onde N começa em 1
}
// Os guards são destruídos aqui quando o escopo termina
// TODO: Print "After scope: X connections available"
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online