Quais são os prós e os contras do uso de exceções em C ++ em relação ao desenvolvimento de jogos.
O guia de estilos do Google diz que eles não usam exceções por vários motivos. As mesmas razões são pertinentes para o desenvolvimento de jogos?
Não usamos exceções em C ++ ... - google-styleguide.googlecode.com
Algumas questões em que pensar.
- Como se refere ao desenvolvimento de bibliotecas usadas em vários projetos.
- Como isso afeta o teste de unidade, teste de integração, etc?
- O que isso faz com o uso de bibliotecas de terceiros.
c++
software-engineering
David Young
fonte
fonte
Respostas:
Também existem algumas conseqüências desagradáveis para o uso de exceções no C ++, se você não souber todos os detalhes de como eles funcionam. Como muitos jogos são escritos em C ++, isso pode ser um fator.
Por exemplo, no C ++, lançar uma exceção em um destruidor pode ter sérias conseqüências. Isso pode causar todos os tipos de comportamento indefinido e falhas, porque você pode ficar preso em uma situação em que há mais de uma exceção ativa em voo. (Consulte o item 8 do C ++ efetivo para obter mais informações sobre isso.)
fonte
Joel sobre as visões do software de exceções.
Visão interessante atualmente não está em nenhuma das respostas,
http://www.joelonsoftware.com/items/2003/10/13.html
fonte
Exceções não são suportadas e, portanto, altamente desencorajadas em pelo menos um ambiente moderno de desenvolvimento de console.
A maioria dos desenvolvedores de console que conheço prefere não usá-los de qualquer maneira, devido à sobrecarga adicional no executável e à filosofia de que eles simplesmente não são necessários. RTTI é visto da mesma maneira.
fonte
De todos os projetos em que trabalhei em jogos, nenhum deles usou exceções. A sobrecarga de chamada de função é o principal motivo. Como mencionado anteriormente, como o RTTI, na maioria dos estúdios não é um assunto de discussão. Não porque a maioria dos codificadores vem de Java / acadêmicos, porque, francamente, a maioria não.
Na verdade, não afeta o teste de unidade, pois você apenas afirma as condições. Para as bibliotecas, você se sai melhor, sem exceções, pois vai forçá-lo a todos que o usam.
Embora torne algumas situações um pouco mais difíceis de lidar, também (imo) força você a ter uma configuração mais controlada, pois as exceções podem levar a abusos. A tolerância a erros é boa, mas não se isso levar à codificação incorreta.
Lembre-se, isso vem de alguém que nunca usou o tratamento de exceções (tudo bem, uma ou duas vezes em ferramentas - não fez isso por mim, acho que é com isso que você cresce).
fonte
function();
que não sabe instantaneamente se um código de erro está sendo ignorado ou não. Provavelmente, é por isso que os idiomas que permitem ignorar um valor de retorno tentam forçá-lo a preferir exceções.Error
classe que é leve e, se ignorada, resulta em uma declaração sendo disparada. Portanto, ele realmente não pode ser ignorado, assim como uma exceção não pode ser ignorada.O mais importante para mim, como desenvolvedor profissional de jogos, é que as exceções precisam ser escritas por pessoas que entendem como as exceções funcionam (que são poucas na indústria de jogos) depuradas por elas também e o código subjacente que as executa (que é uma bagunça ramificada de qualquer maneira e matará seu processador em ordem) teve que ser escrito por pessoas que fornecem seu compilador / vinculador. O problema disso é que eles têm apenas uma quantidade finita de recursos, então se concentram em escrever melhores otimizações e correções para o código que os desenvolvedores de jogos usam ... o que geralmente não é exceção. Se você tem um bug de exceção no hardware moderno com novos compiladores, para quem você vai ligar? seu especialista em exceções que acha que está tudo bem com o código ou o fornecedor que diz: sim, em breve, nós '
Não há nada inerentemente errado com exceções, mas elas não são boas porque a realidade atrapalha a teoria.
fonte
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 tipoT
, não pode nem prever onde estão esses pontos de saída, pois qualquer coisa que envolvaT
poderia ser lançada. Mesmo comparando umT
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.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.
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.
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 chamarstd::terminate
uma exceção. Isso é quase inútil (poderia chamar isso emicantexcept
vez denoexcept
). 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 asnoexcept
funções (que todos os destruidores devam implicitamente) sejam permitidas apenas para chamarnoexcept
funções / operadores da mesma forma e cometer um erro do compilador a partir de qualquer um deles.fonte
Na minha opinião, existem duas razões principais para não ser tradicionalmente usada no desenvolvimento de jogos:
fonte