Menu
Coddy logo textTech

RAII como um 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 de C++ — é um poderoso padrão de projeto que associa 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, network connections, mutexes ou transações de banco de dados.

A ideia central é simples: acquire recursos no constructor e libere-os no destrutor. Como o C++ garante que os destruidores 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 locks em código multithread. O std::lock_guard da biblioteca padrão segue este padrão — ele executa acquire de um mutex na construção e o libera na destruição, evitando deadlocks causados por esquecimentos de unlock.

Ao implementar classes RAII, lembre-se de either usar delete ou implementar corretamente as operações de copy/move (Rule of Five) para evitar a duplicação de recursos ou problemas de liberação dupla. RAII transforma o gerenciamento manual de recursos, propenso a erros, em uma limpeza segura e automática.

challenge icon

Desafio

Fácil

Vamos criar um Connection Pool Manager 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 que ocorram exceções ou que os caminhos do código se tornem 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 ConnectionPool deve controlar quantas conexões estão disponíveis (começando com uma capacidade passada ao constructor). Implemente:

    • acquire() — se uma conexão estiver disponível, diminua a contagem e imprima Connection acquired (X available), em que X é a contagem restante; retorne true se for bem-sucedido e false se não houver conexões disponíveis
    • release() — aumente a contagem disponível e imprima Connection released (X available)
    • available() — retorna o número current de conexões disponíveis
  • ConnectionGuard.h: Crie o wrapper RAII que gerencia com segurança uma única conexão.

    Sua classe ConnectionGuard incorpora o padrão RAII. Ela deve:

    • Receber uma referência a um ConnectionPool em seu constructor e tentar adquirir uma conexão
    • Armazenar se a acquisition foi bem-sucedida
    • Fornecer um método isConnected() para verificar se o guard mantém uma conexão válida
    • Devolver automaticamente a conexão ao pool no destructor (somente se uma tiver sido adquirida)
    • Excluir o copy constructor e a copy assignment para impedir a duplicação de recursos (consideração da Rule of Five)

    Quando o destructor for executado, se uma conexão estiver sendo mantida, imprima Guard releasing connection antes de chamar release no pool.

  • main.cpp: Demonstre a limpeza automática do RAII por meio de escopos.

    Leia duas entradas:

    1. Capacidade do pool (inteiro)
    2. Número de conexões a solicitar (inteiro)

    Create um ConnectionPool com a capacidade fornecida. Em seguida, dentro de um escopo aninhado (usando chaves curly), crie o número solicitado de objetos ConnectionGuard armazenados em um vetor. Para cada guard, imprima se ele se conectou com sucesso:

    • Se conectado: Guard N: Connected
    • Se não conectado: Guard N: Failed to connect

    (em que N começa em 1)

    After o escopo terminar (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 available

Com 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 available

Observe como as conexões são liberadas automatically quando os guards saem do escopo — você nunca chama explicitamente release no seu código principal. Os destructors 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 de recursos acontece automaticamente e de forma confiável, independentemente de como o escopo termina.

Experimente você mesmo

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

using namespace std;

int main() {
    // Leia as 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: 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;
}
quiz iconTeste seus conhecimentos

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

Pratique por conta própria: Compilador de C++ online