Médio C++

Gerenciador de buffer RAII

Enunciado

Implemente uma classe Buffer que aloca um array de int no construtor e o libera no destrutor. A classe deve ter um método set(int index, int value) e get(int index). No main, crie um Buffer de tamanho 5, preencha com valores e imprima. Em seguida, force uma exceção para verificar que a memória é liberada.

Requisitos

  • Alocar memória dinamicamente no construtor com new int[tamanho].
  • Liberar no destrutor com delete[].
  • Desabilitar cópia para evitar dupla liberação.
  • Métodos set e get com verificação de limites (lançar std::out_of_range).

Código inicial

#include <iostream>
#include <stdexcept>

class Buffer {
    int* dados_;
    size_t tamanho_;
public:
    // TODO: construtor, destrutor, set, get
};

int main() {
    // TODO: testar
    return 0;
}

Saída esperada

Buffer de 5 elementos criado
0 10 20 30 40 
Excecao capturada: indice fora dos limites
Buffer destruido
Ver dica

No destrutor, use delete[] dados_;. Para desabilitar cópia, declare Buffer(const Buffer&) = delete; e Buffer& operator=(const Buffer&) = delete;.

Mostrar solução
#include <iostream>
#include <stdexcept>

class Buffer {
    int* dados_;
    size_t tamanho_;
public:
    Buffer(size_t tamanho) : tamanho_(tamanho) {
        dados_ = new int[tamanho];
        std::cout << "Buffer de " << tamanho << " elementos criado\n";
    }

    ~Buffer() {
        delete[] dados_;
        std::cout << "Buffer destruido\n";
    }

    // Desabilita cópia
    Buffer(const Buffer&) = delete;
    Buffer& operator=(const Buffer&) = delete;

    void set(size_t index, int value) {
        if (index >= tamanho_) {
            throw std::out_of_range("indice fora dos limites");
        }
        dados_[index] = value;
    }

    int get(size_t index) const {
        if (index >= tamanho_) {
            throw std::out_of_range("indice fora dos limites");
        }
        return dados_[index];
    }

    size_t tamanho() const { return tamanho_; }
};

int main() {
    try {
        Buffer buf(5);
        for (size_t i = 0; i < buf.tamanho(); ++i) {
            buf.set(i, static_cast<int>(i * 10));
        }
        for (size_t i = 0; i < buf.tamanho(); ++i) {
            std::cout << buf.get(i) << ' ';
        }
        std::cout << '\n';
        // Força exceção: acesso fora dos limites
        buf.set(10, 99);
    } catch (const std::exception& e) {
        std::cerr << "Excecao capturada: " << e.what() << '\n';
    }
    return 0;
}

Passo a passo

  1. A classe Buffer tem um ponteiro int* dados_ e um size_t tamanho_.
  2. O construtor aloca um array de int com new int[tamanho] e imprime uma mensagem.
  3. O destrutor libera a memória com delete[] dados_ e imprime uma mensagem.
  4. Desabilitamos a cópia com = delete para evitar que dois objetos apontem para o mesmo array (o que causaria dupla liberação).
  5. Os métodos set e get verificam se o índice está dentro dos limites; se não, lançam std::out_of_range.
  6. No main, criamos um Buffer de tamanho 5 dentro de um bloco try.
  7. Preenchemos e imprimimos os valores.
  8. Forçamos uma exceção chamando set(10, 99), que está fora dos limites.
  9. A exceção é capturada; o destrutor do Buffer é chamado automaticamente durante o stack unwinding, liberando a memória.

Por que funciona

O destrutor é invocado mesmo quando uma exceção é lançada, desde que o objeto tenha sido completamente construído. Assim, a memória alocada é liberada sem vazamentos. A desabilitação da cópia evita que múltiplos objetos gerenciem o mesmo recurso, o que levaria a double free.

Erros comuns

  • Esquecer delete[]: vazamento de memória. Use delete[] para arrays.
  • Permitir cópia: se você copiar o Buffer, ambos os objetos terão o mesmo ponteiro e o destrutor de cada um tentará liberar a mesma memória, causando double free. Solução: desabilitar cópia ou implementar construtor de cópia profunda.
  • Não verificar limites: acessar fora do array é comportamento indefinido. Sempre valide índices.
  • Lançar exceção no construtor após alocar: se o construtor lançar depois de new, o destrutor não será chamado e a memória vazará. Neste exemplo, não lançamos após alocar, mas em casos reais use std::unique_ptr para evitar isso.

Outra forma de resolver

Usar std::vector<int> em vez de gerenciar memória manualmente. O std::vector já implementa RAII e cuida de tudo.

Saída esperada

Buffer de 5 elementos criado
0 10 20 30 40 
Excecao capturada: indice fora dos limites
Buffer destruido