Quando noexcept não é só moda, mas sua principal ferramenta em 2025
\n\nImagine: você programa em C++ há anos, o compilador não reclama, o código funciona. Mas de repente você percebe que std::vector, ao inserir novos elementos, está copiando objetos, mesmo você tendo definido um move-constructor. Essa dor é familiar? Bem-vindo ao mundo onde noexcept decide o destino da performance.
A pergunta “When should I really use noexcept?” no Stack Overflow acumulou milhares de visualizações — e não é à toa. Em 2025, com compiladores mais inteligentes e a biblioteca padrão ainda mais exigente quanto a garantias, o uso incorreto de noexcept deixa de ser uma micro-otimização e se torna um erro crítico. Vamos entender onde esse especificador é seu melhor amigo e onde é apenas ruído desnecessário.
\n\nPílula de conhecimento: o que é noexcept e para que serve?
\n\nnoexcept é um especificador que diz ao compilador: “Esta função nunca lançará uma exceção”. No C++17, ele se tornou parte do sistema de tipos, e no C++20 ganhou ainda mais influência nas otimizações.
Quando você escreve:
\nvoid funcao_segura() noexcept {\n // código que garante que não lança exceções\n}\n\nO compilador recebe sinal verde para:
\n- \n
- Gerar código mais compacto (sem overhead de desempilhamento) \n
- Otimizar a move-semântica em contêineres \n
- Simplificar a lógica para
std::terminateem caso de exceção real \n
Mas o principal é: noexcept afeta o comportamento dos contêineres padrão. E é aí que mora a pegadinha mais comum.
\n\nRegra nº1: Move-constructor e move-operator de atribuição DEVEM ser noexcept
\n\nEsta é a regra de ouro do C++11 e de todos os padrões seguintes. Se seu move-constructor não estiver marcado como noexcept, o std::vector vai copiar os elementos em vez de movê-los ao realocar memória. Por quê?
Porque std::vector precisa garantir segurança de exceções. Se a operação de move pode lançar uma exceção, o contêiner não consegue reverter ao estado original. Já a cópia é segura: se a cópia falhar, o objeto original permanece intacto.
Exemplo para guardar:
\n#include <vector>\n#include <string>\n#include <iostream>\n\nstruct MeusDados {\n std::string nome;\n \n // Move-constructor sem noexcept\n MeusDados(MeusDados&& outro) : nome(std::move(outro.nome)) {\n // Tudo ok, mas sem noexcept!\n }\n};\n\nint main() {\n std::vector<MeusDados> vec;\n MeusDados d1{"Ana"}, d2{"Bruno"};\n vec.push_back(std::move(d1));\n vec.push_back(std::move(d2)); \n // Atenção: ao realocar memória,\n // os elementos serão COPIADOS, não movidos!\n std::cout << "Tamanho: " << vec.size() << std::endl;\n return 0;\n}\n\nA correção é adicionar noexcept:
MeusDados(MeusDados&& outro) noexcept : nome(std::move(outro.nome)) {}\n\nConclusão: Todas as operações de move que não lançam exceções (e geralmente não lançam, a menos que aloquem memória manualmente) devem ser marcadas com noexcept. Isso não é opcional — é essencial para performance.
Regra nº2: Operações de swap — outro forte candidato a noexcept
\n\nFunções swap são muito usadas em algoritmos e contêineres. Se swap pode lançar uma exceção, muitos algoritmos (como ordenação) perdem eficiência.
class Buffer {\n int* dados;\n size_t tamanho;\npublic:\n friend void swap(Buffer& a, Buffer& b) noexcept {\n using std::swap;\n swap(a.dados, b.dados);\n swap(a.tamanho, b.tamanho);\n }\n};\n\nPor que isso é importante? Porque std::sort, std::shuffle e outros algoritmos da STL dependem de swap para funcionar de forma eficiente. Um swap noexcept permite que eles operem com segurança e desempenho máximos.