Eu tendo a adicionar muitas asserções ao meu código C ++ para tornar a depuração mais fácil sem afetar o desempenho das compilações. Agora,assert
é uma macro C pura projetada sem mecanismos C ++ em mente.
Por outro lado std::logic_error
, C ++ define , que deve ser lançado nos casos em que há um erro na lógica do programa (daí o nome). Lançar uma instância pode ser a alternativa perfeita e mais C ++ para assert
.
O problema é que assert
e abort
ambos terminam o programa imediatamente sem chamar destruidores, portanto, ignorando a limpeza, enquanto lançar uma exceção manualmente adiciona custos de tempo de execução desnecessários. Uma maneira de contornar isso seria criar uma macro de asserção própria SAFE_ASSERT
, que funciona exatamente como a contraparte C, mas lança uma exceção em caso de falha.
Posso pensar em três opiniões sobre este problema:
- Atenha-se à afirmação de C. Como o programa é encerrado imediatamente, não importa se as alterações foram desenroladas corretamente. Além disso, usar
#define
s em C ++ é tão ruim quanto. - Lance uma exceção e capture-a em main () . Permitir que o código ignore destruidores em qualquer estado do programa é uma prática ruim e deve ser evitado a todo custo, assim como as chamadas para terminate (). Se exceções são lançadas, elas devem ser capturadas.
- Lance uma exceção e deixe-o encerrar o programa. Uma exceção ao encerrar um programa está bem e, devido a
NDEBUG
, isso nunca acontecerá em uma versão de construção. A captura é desnecessária e expõe detalhes de implementação do código interno amain()
.
Existe uma resposta definitiva para este problema? Alguma referência profissional?
Editado: Ignorar destruidores, obviamente, não é um comportamento indefinido.
fonte
logic_error
é o erro lógico. Um erro na lógica do programa é chamado de bug. Você não resolve bugs lançando exceções.static_assert
onde for apropriado, se tiver disponível.std::bug
?std::abort()
; ele apenas levantará um sinal que fará com que o processo seja encerrado.Respostas:
As asserções são inteiramente apropriadas no código C ++. As exceções e outros mecanismos de tratamento de erros não têm o mesmo objetivo que as asserções.
O tratamento de erros é para quando há um potencial para recuperar ou relatar um erro de forma adequada ao usuário. Por exemplo, se houver um erro ao tentar ler um arquivo de entrada, você pode querer fazer algo a respeito. Erros podem resultar de bugs, mas também podem ser simplesmente a saída apropriada para uma determinada entrada.
Asserções são para coisas como verificar se os requisitos de uma API são atendidos quando a API normalmente não seria verificada, ou para verificar coisas que o desenvolvedor acredita estar garantido pela construção. Por exemplo, se um algoritmo requer entrada classificada, você normalmente não verificaria isso, mas você pode ter uma declaração para verificá-lo para que as compilações de depuração sinalizem esse tipo de bug. Uma afirmação deve sempre indicar um programa operando incorretamente.
Se você está escrevendo um programa em que um desligamento incorreto pode causar um problema, convém evitar afirmações. O comportamento indefinido estritamente em termos da linguagem C ++ não se qualifica como um problema aqui, uma vez que acertar uma asserção provavelmente já é o resultado de um comportamento indefinido ou a violação de algum outro requisito que poderia impedir que alguma limpeza funcionasse corretamente.
Além disso, se você implementar asserções em termos de uma exceção, ela poderá ser potencialmente capturada e 'tratada', embora isso contradiga o próprio propósito da asserção.
fonte
3
invés do1
seu código, em geral, isso não deve acionar uma asserção. As asserções são apenas erros do programador, não do usuário da biblioteca ou erro do aplicativo.As afirmações são para depuração . O usuário do código enviado nunca deve vê-los. Se uma asserção for atingida, seu código precisará ser corrigido.
As exceções são para circunstâncias excepcionais . Se um for encontrado, o usuário não poderá fazer o que deseja, mas poderá continuar em outro lugar.
O tratamento de erros é para o fluxo normal do programa. Por exemplo, se você solicitar ao usuário um número e obter algo que não pode ser analisado, isso é normal , porque a entrada do usuário não está sob seu controle e você sempre deve lidar com todas as situações possíveis como algo natural. (Por exemplo, faça um loop até obter uma entrada válida, dizendo "Desculpe, tente novamente" no meio.)
fonte
As asserções podem ser usadas para verificar invariantes de implementação internos, como estado interno antes ou depois da execução de algum método, etc. Se a asserção falhar, isso realmente significa que a lógica do programa está quebrada e você não pode se recuperar disso. Nesse caso, o melhor que você pode fazer é interromper o mais rápido possível, sem passar exceção para o usuário. O que é realmente bom sobre asserções (pelo menos no Linux) é que o dump de núcleo é gerado como resultado do encerramento do processo e, portanto, você pode investigar facilmente o rastreamento de pilha e as variáveis. Isso é muito mais útil para entender a falha lógica do que a mensagem de exceção.
fonte
Não rodar destruidores devido a alling abort () não é um comportamento indefinido!
Se fosse, seria um comportamento indefinido chamar
std::terminate()
também e, portanto, de que adiantaria fornecê-lo?assert()
é tão útil em C ++ quanto em C. As afirmações não são para tratamento de erros, mas para abortar o programa imediatamente.fonte
abort()
é para abortar o programa imediatamente. Você está certo que as asserções não são para tratamento de erros, entretanto, assert tenta lidar com o erro abortando. Em vez disso, você não deveria lançar uma exceção e permitir que o chamador trate o erro, se puder? Afinal, o chamador está em uma posição melhor para determinar se a falha de uma função faz com que não valha a pena fazer outra coisa. Talvez o chamador esteja tentando fazer três coisas não relacionadas e ainda possa concluir as outras duas tarefas e simplesmente descartar esta.assert
está definido para chamarabort
(quando a condição é falsa). Quanto a lançar exceções, não, nem sempre é apropriado. Algumas coisas não podem ser tratadas pelo chamador. O chamador não pode determinar se um bug lógico em uma função de biblioteca de terceiros é recuperável ou se os dados corrompidos podem ser corrigidos.IMHO, as afirmações são para verificar as condições que, se violadas, tornam todo o resto um absurdo. E, portanto, você não pode se recuperar deles, ou melhor, a recuperação é irrelevante.
Eu os agruparia em 2 categorias:
Ambos são exemplos triviais, mas não muito longe da realidade. Por exemplo, pense em algoritmos ingênuos que retornam índices negativos para usar com vetores. Ou programas embutidos em hardware personalizado. Ou melhor, porque sh * t acontece .
E se houver esses erros de desenvolvimento, você não deve estar confiante sobre qualquer mecanismo de recuperação ou tratamento de erros implementado. O mesmo se aplica a erros de hardware.
fonte