Como outros apontaram, esse assert
é o seu último bastião de defesa contra erros de programadores que nunca devem acontecer. São verificações de sanidade que, esperançosamente, não devem falhar esquerda e direita no momento em que você envia.
Ele também foi projetado para ser omitido nas versões de versões estáveis, por qualquer motivo que os desenvolvedores possam achar úteis: estética, desempenho, o que quiserem. É parte do que separa uma compilação de depuração de uma compilação de lançamento e, por definição, uma compilação de lançamento é desprovida de tais asserções. Portanto, há uma subversão do design, se você deseja liberar o analógico "release build com assertions in place", que seria uma tentativa de um release build com uma _DEBUG
definição de pré - processador e sem NDEBUG
definição; não é realmente uma versão mais compilada.
O design se estende até a biblioteca padrão. Como um exemplo muito básico entre inúmeras, muitas implementações de uma verificação de sanidade std::vector::operator[]
irão assert
garantir que você não esteja verificando o vetor fora dos limites. E a biblioteca padrão começará a ter um desempenho muito pior se você habilitar essas verificações em uma versão. Uma referência de vector
usooperator[]
e um preenchedor com essas afirmações incluídas em uma matriz dinâmica antiga simples geralmente mostra que a matriz dinâmica é consideravelmente mais rápida até você desabilitar essas verificações, de modo que elas afetam o desempenho de maneiras distantes e distantes. Uma verificação de ponteiro nulo aqui e uma verificação fora dos limites podem realmente se tornar uma despesa enorme se essas verificações estiverem sendo aplicadas milhões de vezes em todos os quadros em loops críticos que precedem o código, tão simples quanto desreferenciar um ponteiro inteligente ou acessar uma matriz.
Portanto, é mais provável que você deseje uma ferramenta diferente para o trabalho e uma que não seja projetada para ser omitida nas compilações de versões, se você quiser que compilações de versões que executam tais verificações de sanidade em áreas-chave. O mais útil que eu pessoalmente acho é o log. Nesse caso, quando um usuário relata um bug, as coisas ficam muito mais fáceis se eles anexam um log e a última linha do log me dá uma grande pista de onde o bug ocorreu e qual pode ser. Em seguida, ao reproduzir suas etapas em uma compilação de depuração, da mesma forma, eu poderia ter uma falha de afirmação, e essa falha de afirmação ainda me dá grandes pistas para otimizar meu tempo. No entanto, como o registro é relativamente caro, não o uso para aplicar verificações de sanidade de nível extremamente baixo, como garantir que uma matriz não seja acessada fora dos limites em uma estrutura de dados genérica.
No entanto, finalmente, e um pouco de acordo com você, eu pude ver um caso razoável em que você pode realmente entregar aos testadores algo semelhante a uma compilação de depuração durante o teste alfa, por exemplo, com um pequeno grupo de testadores alfa que, digamos, assinaram um NDA . Lá, ele pode otimizar o teste alfa se você entregar a seus testadores algo diferente de uma versão completa com algumas informações de depuração anexadas, além de alguns recursos de depuração / desenvolvimento, como testes que eles podem executar e uma saída mais detalhada enquanto executam o software. Eu já vi pelo menos algumas grandes empresas de jogos fazendo coisas assim para o alfa. Mas isso é algo como testes alfa ou internos, nos quais você está realmente tentando dar aos testadores algo diferente de uma versão compilada. Se você está realmente tentando enviar uma versão compilada, por definição, não deve ter_DEBUG
definido ou isso realmente está confundindo a diferença entre uma compilação "debug" e "release".
Por que esse código foi removido antes do lançamento? As verificações não são muito prejudiciais ao desempenho e, se falharem, há definitivamente um problema sobre o qual eu prefiro uma mensagem de erro mais direta.
Como indicado acima, as verificações não são necessariamente triviais do ponto de vista de desempenho. Muitos são provavelmente triviais, mas, novamente, até a biblioteca padrão os usa e isso pode afetar o desempenho de maneiras inaceitáveis para muitas pessoas em muitos casos, se, digamos, a passagem de acesso aleatório std::vector
demorar 4 vezes mais do que deveria ser uma versão otimizada do build por causa de sua verificação de limites que nunca deveria falhar.
Em uma equipe anterior, na verdade, tivemos que fazer com que nossa biblioteca de matrizes e vetores excluísse algumas afirmações em certos caminhos críticos, apenas para acelerar a compilação de depuração, porque essas afirmações estavam diminuindo as operações matemáticas em uma ordem de grandeza até o ponto em que estavam. começando a exigir que esperemos 15 minutos antes que pudéssemos rastrear o código de interesse. Meus colegas queriam apenas remover oasserts
diretamente porque descobriram que apenas fazer isso fazia uma diferença enorme. Em vez disso, decidimos apenas fazer com que os caminhos críticos de depuração os evitassem. Quando fizemos esses caminhos críticos usarem os dados vetoriais / matriz diretamente, sem passar pela verificação de limites, o tempo necessário para executar a operação completa (que incluía mais do que apenas matemática vetorial / matriz) reduzido de minutos para segundos. Portanto, esse é um caso extremo, mas definitivamente as afirmações nem sempre são insignificantes do ponto de vista de desempenho, nem mesmo próximas.
Mas também é assim que asserts
são projetados. Se eles não tiveram um impacto de desempenho tão grande em todos os aspectos, talvez eu seja a favor se eles foram projetados como mais do que um recurso de compilação de depuração ou podemos usar o vector::at
que inclui a verificação de limites, mesmo em versões de lançamento e lançamentos fora dos limites acesso, por exemplo (ainda com um enorme impacto no desempenho). Mas atualmente acho o design deles muito mais útil, dado o enorme impacto no desempenho dos meus casos, como um recurso somente de depuração, que é omitido quando NDEBUG
definido. Para os casos com os quais trabalhei, pelo menos, faz uma diferença enorme a criação de uma versão excluir as verificações de sanidade que nunca deveriam realmente falhar em primeiro lugar.
vector::at
vs. vector::operator[]
Eu acho que a distinção desses dois métodos está no centro disso e da alternativa: exceções. vector::operator[]
implementações normalmente assert
para garantir que o acesso fora dos limites acione um erro facilmente reproduzível ao tentar acessar um vetor fora dos limites. Mas os implementadores da biblioteca fazem isso com o pressuposto de que não custará um centavo em uma compilação de versão otimizada.
Enquanto isso, vector::at
é fornecido o que sempre verifica e lança fora dos limites, mesmo nas compilações de versão, mas tem uma penalidade de desempenho até o ponto em que geralmente vejo muito mais código usando do vector::operator[]
que vector::at
. Muito do design do C ++ faz eco à idéia de "pagar pelo que você usa / precisa", e muitas pessoas são a favor operator[]
, o que nem se importa com a verificação de limites nas versões de lançamento, com base na noção de que eles não não precisa verificar os limites em suas compilações de versão otimizadas. De repente, se as asserções fossem ativadas nas compilações de versão, o desempenho dessas duas seria idêntico e o uso do vetor sempre acabaria sendo mais lento que um array dinâmico. Portanto, grande parte do design e dos benefícios das asserções é baseada na idéia de que elas se tornam livres em uma versão compilada.
release_assert
Isso é interessante depois de descobrir essas intenções. Naturalmente, os casos de uso de todos seriam diferentes, mas acho que encontraria algum uso para um release_assert
que faça a verificação e trava o software, mostrando um número de linha e uma mensagem de erro, mesmo nas versões lançadas.
Seria para alguns casos obscuros no meu caso que eu não quero que o software se recupere normalmente, como faria se uma exceção fosse lançada. Eu gostaria que ele falhasse mesmo na versão nesses casos, para que o usuário possa receber um número de linha para relatar quando o software encontrou algo que nunca deveria acontecer, ainda no campo das verificações de sanidade quanto a erros do programador, não erros externos de entrada como exceções, mas barato o suficiente para ser feito sem se preocupar com seu custo no lançamento.
Na verdade, existem alguns casos em que eu considerava uma falha grave com um número de linha e uma mensagem de erro preferíveis à recuperação normal de uma exceção lançada que pode ser barata o suficiente para manter em uma versão. E há alguns casos em que é impossível recuperar de uma exceção, como um erro encontrado ao tentar recuperar de uma existente. Lá, eu encontraria um ajuste perfeito para, release_assert(!"This should never, ever happen! The software failed to fail!");
naturalmente, que seria muito barato, já que a verificação seria realizada dentro de um caminho excepcional em primeiro lugar e não custaria nada nos caminhos normais de execução.