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
adicionardeve lançarstd::bad_allocse o valor for negativo e não modificar o vetor.size()eoperator[]devem ser marcadosnoexcept.- O
maindeve 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
- Incluímos
<new>para usarstd::bad_alloc. - A classe
Bufferencapsula umstd::vector<int>privado. adicionarprimeiro verifica se o valor é negativo; se for, lançastd::bad_allocantes de tocar no vetor.- Se a verificação passar,
push_backé chamado; como o vetor não foi modificado antes, a garantia forte é preservada. size()eoperator[]são marcadosnoexceptporque não lançam e não alocam.- No
main, adicionamos 1, 2, 3 e imprimimos o conteúdo. - Tentamos adicionar
-5, capturamosstd::bad_alloce imprimimos a mensagem. - 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
noexceptem função que lança: seadicionarfossenoexcepte lançasse, o programa chamariastd::terminate. - Capturar por valor:
catch (std::bad_alloc e)causa slicing; useconst std::bad_alloc&. - Esquecer de incluir
<new>:std::bad_allocnã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