Para mim, o tratamento de exceções pode ser ótimo se tudo estiver em conformidade com o RAII e você estiver reservando exceções para caminhos verdadeiramente excepcionais (por exemplo, um arquivo corrompido sendo lido) e você nunca precisará ultrapassar os limites do módulo e toda a sua equipe estará a bordo. ......
Zero Cost EH
Atualmente, a maioria dos compiladores implementa manipulação de exceção de custo zero, o que pode torná-lo ainda mais barato do que se ramificar manualmente em condições de erro em caminhos de execução regulares, embora, em troca, torne os caminhos excepcionais extremamente caros (há também algum inchaço nele, embora provavelmente não seja mais). do que se você tratasse completamente todos os erros possíveis manualmente). O fato de arremessar ser muito caro não deve importar, se as exceções estiverem sendo usadas da maneira como são planejadas em C ++ (para circunstâncias verdadeiramente excepcionais que não devem ocorrer em condições normais).
Reversão de efeitos colaterais
Dito isto, tornar tudo conforme o RAII é muito mais fácil dizer do que fazer. É fácil para os recursos locais armazenados em uma função envolvê-los em objetos C ++ com destruidores que se limpam, mas não é tão fácil escrever lógica de desfazer / retroceder para cada efeito colateral único que poderia ocorrer em todo o software. Apenas considere o quão difícil é escrever um contêiner como std::vector
a sequência mais simples de acesso aleatório para ser perfeitamente seguro para exceções. Agora multiplique essa dificuldade por todas as estruturas de dados de um software de larga escala inteiro.
Como exemplo básico, considere uma exceção encontrada no processo de inserção de itens em um gráfico de cena. Para recuperar adequadamente essa exceção, pode ser necessário desfazer essas alterações no gráfico de cena e restaurar o sistema para um estado como se a operação nunca tivesse ocorrido. Isso significa remover as crianças inseridas no gráfico de cena automaticamente ao encontrar uma exceção, talvez de um guarda de escopo.
Fazer isso minuciosamente em um software que causa muitos efeitos colaterais e lida com muitos estados persistentes é muito mais fácil falar do que fazer. Muitas vezes, acho que a solução pragmática é não se incomodar com isso no interesse de enviar as coisas. Com o tipo certo de base de código que possui algum tipo de sistema central de desfazer e, talvez, estruturas de dados persistentes e o número mínimo de funções que causam efeitos colaterais, você pode conseguir segurança de exceção em todos os aspectos ... mas é muito tudo o que você precisa se tudo o que você vai fazer é tentar tornar seu código protegido contra exceções em qualquer lugar.
Há algo praticamente errado lá porque a reversão conceitual dos efeitos colaterais é um problema difícil, mas é mais difícil no C ++. Às vezes, é mais fácil reverter os efeitos colaterais em C em resposta a um código de erro do que fazê-lo em resposta a uma exceção em C ++. Um dos motivos é que qualquer ponto específico de qualquer função poderia potencialmente throw
em C ++. Se você está tentando escrever um contêiner genérico que trabalha com o tipo T
, não pode nem prever onde estão esses pontos de saída, pois qualquer coisa que envolva T
poderia ser lançada. Mesmo comparando um T
com o outro para a igualdade poderia jogar. Seu código precisa lidar com todas as possibilidades , e o número de possibilidades se multiplica exponencialmente em C ++, enquanto em C, elas são muito menos numerosas.
Falta de padrões
Outro problema do tratamento de exceções é a falta de padrões centrais. Esperamos que todos concordem que os destruidores nunca devem ser lançados, uma vez que as reversões nunca falharão, mas isso apenas cobre um caso patológico que geralmente travaria o software se você violasse a regra.
Deveria haver alguns padrões mais sensíveis, como nunca jogar de um operador de comparação (todas as funções que são logicamente imutáveis nunca deveriam jogar), nunca jogar de um controlador de movimentação, etc. Essas garantias facilitariam muito a gravação de códigos com exceção de segurança, mas não temos tais garantias. Nós temos que seguir a regra de que tudo pode ser jogado - se não agora, então possivelmente no futuro.
Pior, pessoas de diferentes origens linguísticas encaram as exceções de maneira muito diferente. No Python, eles realmente têm essa filosofia de "pular antes de olhar", que envolve disparar exceções em caminhos de execução regulares! Quando você faz com que esses desenvolvedores escrevam código C ++, você pode observar exceções sendo lançadas para a esquerda e direita para coisas que não são realmente excepcionais, e lidar com tudo isso pode ser um pesadelo se você estiver tentando escrever código genérico, por exemplo
Manipulação de erros
O tratamento de erros tem a desvantagem de que você deve verificar todos os erros possíveis que possam ocorrer. Mas há uma vantagem de que, às vezes, é possível verificar erros um pouco tardiamente, como glGetError
para reduzir significativamente os pontos de saída em uma função, além de torná-los explícitos. Às vezes, você pode continuar executando o código um pouco mais antes de verificar, propagar e se recuperar de um erro, e às vezes isso pode ser realmente mais fácil do que o tratamento de exceções (especialmente com a reversão de efeitos colaterais). Mas você pode nem se incomodar tanto com os erros em um jogo, talvez apenas desligando o jogo com uma mensagem se encontrar coisas como arquivos corrompidos, erros de falta de memória etc.
Como se refere ao desenvolvimento de bibliotecas usadas em vários projetos.
Uma parte desagradável das exceções nos contextos DLL é que você não pode lançar exceções com segurança de um módulo para outro, a menos que possa garantir que elas sejam criadas pelo mesmo compilador, use as mesmas configurações etc. Então, se você estiver escrevendo como uma arquitetura de plug-in destinado a pessoas a serem usadas em todos os tipos de compiladores e, possivelmente, até mesmo em diferentes idiomas, como Lua, C, C #, Java etc., muitas vezes as exceções começam a se tornar um grande incômodo, já que você precisa engolir todas as exceções e traduzi-las em erro códigos de qualquer maneira em todo o lugar.
O que isso faz com o uso de bibliotecas de terceiros.
Se eles são dylibs, eles não podem lançar exceções com segurança pelos motivos mencionados acima. Eles teriam que usar códigos de erro, o que também significa que você, usando a biblioteca, precisaria procurar constantemente códigos de erro. Eles podem agrupar seu dylib com uma biblioteca de empacotador C ++ vinculada estaticamente, criada por você, que traduz os códigos de erro do dylib em exceções lançadas (basicamente lançando do seu binário para o seu binário). Geralmente, acho que a maioria das bibliotecas de terceiros nem deveria se incomodar e se ater aos códigos de erro se usarem dylibs / bibliotecas compartilhadas.
Como isso afeta o teste de unidade, teste de integração, etc?
Geralmente, não encontro equipes testando caminhos excepcionais / de erro de seu código. Eu costumava fazer isso e, na verdade, tinha um código que poderia ser recuperado corretamente bad_alloc
porque queria tornar meu código ultra-robusto, mas não ajuda muito quando sou o único a fazer isso em uma equipe. No final, parei de me incomodar. Eu imagino para um software de missão crítica que equipes inteiras possam verificar para garantir que seu código se recupere robusto de exceções e erros em todos os casos possíveis, mas é muito tempo para investir. O tempo faz sentido em software de missão crítica, mas talvez não seja um jogo.
Amor / Ódio
As exceções também são o meu tipo de recurso de amor / ódio do C ++ - um dos recursos de maior impacto na linguagem, tornando o RAII mais um requisito do que uma conveniência, pois muda a maneira como você precisa escrever o código de cabeça para baixo, digamos , C para lidar com o fato de que qualquer linha de código pode ser um ponto de saída implícito para uma função. Às vezes, quase desejava que o idioma não tivesse exceções. Mas há momentos em que considero as exceções realmente úteis, mas elas são um fator predominante para mim, se eu optar por escrever um pedaço de código em C ou C ++.
Seriam exponencialmente mais úteis para mim se o comitê padrão realmente focasse bastante no estabelecimento de padrões ABI que permitissem que exceções fossem lançadas com segurança entre módulos, pelo menos do código C ++ ao código C ++. Seria incrível se você pudesse atravessar idiomas com segurança, embora isso seja meio que um sonho. A outra coisa que tornaria as exceções exponencialmente mais úteis para mim é se as noexcept
funções realmente causaram um erro do compilador se tentassem invocar qualquer funcionalidade que pudesse ser lançada em vez de apenas chamar std::terminate
uma exceção. Isso é quase inútil (poderia chamar isso em icantexcept
vez de noexcept
). Seria muito mais fácil garantir que todos os destruidores sejam protegidos contra exceções, por exemplo, senoexcept
realmente causou um erro do compilador se o destruidor fizesse algo que pudesse lançar. Se isso é muito caro para determinar minuciosamente em tempo de compilação, apenas faça com que as noexcept
funções (que todos os destruidores devam implicitamente) sejam permitidas apenas para chamar noexcept
funções / operadores da mesma forma e cometer um erro do compilador a partir de qualquer um deles.