Em C++, eu marco uma função como noexcept
nos seguintes casos:
A função em si não gera exceções, mas seus parâmetros de tipo de valor podem gerar exceções durante a construção.
O código a seguir demonstra essa situação:
struct A {
A(bool will_throw) {
std::cerr << "Ctor of A\n";
if (will_throw) throw std::runtime_error("An exception from A");
}
A(A&& lhs) noexcept {
std::cerr << "Move ctor of A\n";
}
~A() noexcept = default;
};
struct B {
A mem_;
// Exceptions are thrown during parameter passing, not inside the function
// Thus it isn't an UB I think.
B(A value = A(true)) noexcept : mem_{std::move(value)} {/* do nothing */}
~B() noexcept = default;
};
Neste código, o construtor de B
é marcado como noexcept
, e o A(true)
construtor padrão pode lançar uma exceção, mas acredito que isso não leva a um comportamento indefinido, pois a exceção ocorre durante a passagem de parâmetros e não dentro do corpo da função.
O programa de teste está aqui .
Minha pergunta é:
Em casos semelhantes, é seguro marcar o construtor e outras funções com noexcept
? Essa prática pode ser aplicada amplamente, especialmente em casos em que a função em si não lança nenhuma exceção?
Tentar propagar uma exceção de uma
noexcept
função não causa UB. Ele causastd::terminate
ser chamado.Se você não quiser
std::terminate
ser chamado, então ainda é seguro para umanoexcept
função aceitar um tipo de parâmetro cuja inicialização pode gerar. Veja [expr.call]/6Em C++, a palavra-chave noexcept é usada para declarar que uma função tem garantia de não lançar exceções. Essa garantia permite que o compilador otimize o código de forma mais eficaz e fornece expectativas mais claras sobre o comportamento de uma função, especialmente em contextos como operações de movimentação dentro de contêineres de biblioteca padrão.
Ao aplicar noexcept, é importante diferenciar entre exceções que ocorrem dentro do corpo da função e aquelas que acontecem durante a passagem de parâmetros. A especificação noexcept se aplica somente às operações internas da função. Por exemplo, no código fornecido, o construtor de B é marcado como noexcept, o que é seguro porque quaisquer exceções potenciais da construção do parâmetro padrão A(true) ocorrem antes que o corpo do construtor seja executado e não violam a garantia noexcept do próprio construtor de B.
As melhores práticas para usar noexcept incluem garantir que todas as operações dentro de uma função sejam livres de exceções antes de marcá-la como noexcept. Isso é particularmente recomendado para construtores de movimentação e operadores de atribuição de movimentação para melhorar o desempenho de contêineres de biblioteca padrão como std::vector . Além disso, usar noexcept condicional em modelos pode fornecer flexibilidade ao permitir que garantias de exceção dependam de parâmetros de modelo. No entanto, marcar incorretamente uma função que pode lançar exceções como noexcept pode levar ao encerramento inesperado do programa por meio de std::terminate , portanto, consideração cuidadosa e testes completos são essenciais.