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 C++ - é um padrão de projeto poderoso que vincula o gerenciamento de recursos ao tempo de vida do objeto. Você já viu RAII com smart pointers, mas o padrão se aplica a qualquer recurso: manipuladores de arquivos, conexões de rede, mutexes ou transações de banco de dados.

A ideia central é simples: adquirir recursos no construtor, liberá-los no destrutor. Como o C++ garante que os destrutores sejam executados quando os objetos saem de escopo, a limpeza ocorre 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";
}

O RAII brilha ao gerenciar locks 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, prevenindo deadlocks decorrentes de desbloqueios esquecidos.

Ao implementar classes RAII, lembre-se de excluir ou implementar adequadamente as operações de cópia/movimentação (Regra dos Cinco) para evitar problemas de duplicação de recursos ou liberação dupla. O RAII transforma o gerenciamento manual de recursos, propenso a erros, em uma limpeza automática e segura.

challenge icon

Desafio

Fácil

Vamos construir 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 caros que devem 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 gerencia um número limitado de conexões.

    Sua classe ConnectionPool deve rastrear quantas conexões estão disponíveis (comece com uma capacidade passada ao construtor). Implemente:

    • acquire() — se uma conexão estiver disponível, diminua a contagem e imprima "Connection acquired (X available)" onde X é a contagem restante; retorne true se for bem-sucedido, 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 atual de conexões disponíveis
  • ConnectionGuard.h: Construa 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 para um ConnectionPool em 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 possui uma conexão válida
    • Liberar automaticamente a conexão de volta ao pool no destruidor (apenas se uma foi adquirida)
    • Deletar o construtor de cópia e a atribuição de cópia para evitar a duplicação de recursos (consideração da Regra dos Cinco)

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

  • main.cpp: Demonstre a limpeza automática do RAII através de escopos.

    Leia duas entradas:

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

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

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

    (onde N começa em 1)

    Após o término do escopo (os guards são 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 automaticamente quando os guards saem de escopo — você nunca chama release explicitamente em seu código principal. Os destruidores são executados na ordem inversa da construção (o último guard criado é destruído primeiro), e cada conexão adquirida tem a garantia de ser devolvida. Este é o poder do RAII: a limpeza de recursos acontece de forma automática e confiável, não importa como o escopo termine.

Folha de consulta

RAII (Resource Acquisition Is Initialization) é um padrão de projeto que vincula o gerenciamento de recursos ao tempo de vida do objeto. Os recursos são adquiridos no construtor e liberados no destruidor.

Como o C++ garante que os destruidores sejam executados quando os objetos saem de escopo, a limpeza ocorre automaticamente — mesmo quando ocorrem exceções:

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");
    }  // Destruidor chamado aqui - arquivo fechado automaticamente
    
    std::cout << "After scope\n";
}

O RAII é particularmente útil para gerenciar locks em código multithread. O std::lock_guard da biblioteca padrão adquire um mutex na construção e o libera na destruição, evitando deadlocks causados por esquecimentos de desbloqueio.

Ao implementar classes RAII, delete ou implemente adequadamente as operações de cópia/movimentação (Regra dos Cinco) para evitar problemas de duplicação de recursos ou de liberação dupla.

Experimente você mesmo

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

using namespace std;

int main() {
    // Lê as entradas
    int capacity;
    int numConnections;
    cin >> capacity;
    cin >> numConnections;
    
    // TODO: Criar um ConnectionPool com a capacidade fornecida
    
    // TODO: Criar um escopo aninhado usando chaves
    {
        // TODO: Criar um vector para armazenar objetos ConnectionGuard
        // Dica: Você precisará usar ponteiros ou smart pointers, já que ConnectionGuard
        // possui o construtor de cópia deletado
        
        // TODO: Fazer um loop para criar numConnections guards
        // Para cada guard, imprima:
        // "Guard N: Connected" ou "Guard N: Failed to connect"
        // onde N começa em 1
        
    }
    // Os guards são destruídos aqui quando o escopo termina
    
    // TODO: Imprimir "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