Desafio C++

Garantia forte com noexcept e rollback

Enunciado

Implemente uma classe Buffer que mantém um std::vector<int> e oferece adicionar(int). A operação deve ter garantia forte: se a alocação falhar (simule com throw std::bad_alloc quando o valor for negativo), o buffer deve permanecer inalterado. Marque size() e operator[] como noexcept. No main, demonstre que após uma falha o buffer continua com os dados originais.

Requisitos

  • adicionar deve lançar std::bad_alloc se o valor for negativo e não modificar o vetor.
  • size() e operator[] devem ser marcados noexcept.
  • O main deve capturar a exceção e imprimir o conteúdo do buffer antes e depois da falha.
  • O programa deve compilar sem avisos com -Wall -Wextra.

Código inicial

#include <iostream>
#include <vector>
#include <stdexcept>

class Buffer {
    std::vector<int> dados_;
public:
    void adicionar(int valor) {
        // TODO: garanta a exceção forte
    }
    std::size_t size() const noexcept { return dados_.size(); }
    int operator[](std::size_t i) const noexcept { return dados_[i]; }
};

int main() {
    // TODO: demonstre a garantia forte
    return 0;
}

Saída esperada

Antes: 1 2 3 
Tentando adicionar -5...
Erro: bad_alloc
Depois: 1 2 3
Ver dica

Para garantir rollback, faça a verificação antes de qualquer modificação. std::vector::push_back já oferece garantia forte se o tipo for noexcept no move, mas aqui a falha é simulada antes.

Mostrar solução
#include <iostream>
#include <vector>
#include <stdexcept>
#include <new> // std::bad_alloc

class Buffer {
    std::vector<int> dados_;
public:
    // Garantia forte: verifica antes de modificar
    void adicionar(int valor) {
        if (valor < 0)
            throw std::bad_alloc{}; // simula falha de alocação
        dados_.push_back(valor);    // só modifica se não lançar
    }

    std::size_t size() const noexcept { return dados_.size(); }
    int operator[](std::size_t i) const noexcept { return dados_[i]; }
};

int main() {
    Buffer b;
    b.adicionar(1);
    b.adicionar(2);
    b.adicionar(3);

    std::cout << "Antes: ";
    for (std::size_t i = 0; i < b.size(); ++i)
        std::cout << b[i] << ' ';
    std::cout << '\n';

    std::cout << "Tentando adicionar -5...\n";
    try {
        b.adicionar(-5);
    } catch (const std::bad_alloc& e) {
        std::cout << "Erro: " << e.what() << '\n';
    }

    std::cout << "Depois: ";
    for (std::size_t i = 0; i < b.size(); ++i)
        std::cout << b[i] << ' ';
    std::cout << '\n';
    return 0;
}

Passo a passo

  1. Incluímos <new> para usar std::bad_alloc.
  2. A classe Buffer encapsula um std::vector<int> privado.
  3. adicionar primeiro verifica se o valor é negativo; se for, lança std::bad_alloc antes de tocar no vetor.
  4. Se a verificação passar, push_back é chamado; como o vetor não foi modificado antes, a garantia forte é preservada.
  5. size() e operator[] são marcados noexcept porque não lançam e não alocam.
  6. No main, adicionamos 1, 2, 3 e imprimimos o conteúdo.
  7. Tentamos adicionar -5, capturamos std::bad_alloc e imprimimos a mensagem.
  8. Imprimimos novamente o buffer, que continua com 1, 2, 3.

Por que funciona

A garantia forte exige que, se uma exceção for lançada, o objeto permaneça no estado original. Aqui, a validação ocorre antes de qualquer modificação, então o vetor nunca é alterado no caminho de falha. noexcept em size() e operator[] informa ao compilador que essas operações não lançam, permitindo otimizações e garantindo segurança em contextos que exigem não-lançamento.

Erros comuns

  • Modificar antes de validar: dados_.push_back(valor); if (valor < 0) throw ...; deixa o vetor com o valor inválido após a exceção, violando a garantia forte.
  • Marcar noexcept em função que lança: se adicionar fosse noexcept e lançasse, o programa chamaria std::terminate.
  • Capturar por valor: catch (std::bad_alloc e) causa slicing; use const std::bad_alloc&.
  • Esquecer de incluir <new>: std::bad_alloc não é declarado sem esse cabeçalho.

Outra forma de resolver

Poderia usar std::vector::reserve para pré-alocar e evitar realocações, mas a garantia forte já é oferecida por push_back para tipos noexcept-move. A validação antecipada é a forma mais simples de garantir rollback.

Saída esperada

Antes: 1 2 3 
Tentando adicionar -5...
Erro: bad_alloc
Depois: 1 2 3