Lançando exceções em DLLs de jogos em C ++? Prós e contras


8

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.

3
Estranhamente, ninguém tinha profissionais
David Young

Respostas:


7

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.)


4
+1 para C ++ eficaz. Lendo a 3ª edição enquanto falamos. Exceção está bem comigo, desde que você seja muito rigoroso sobre onde usá-los, onde os captura e pode garantir que qualquer código de terceiros faça o mesmo.
Anthony

9

Joel sobre as visões do software de exceções.

Visão interessante atualmente não está em nenhuma das respostas,

O raciocínio é que considero as exceções não melhores que as "goto's", consideradas prejudiciais desde os anos 1960, na medida em que criam um salto abrupto de um ponto do código para outro. Na verdade, eles são significativamente piores que os de Goto:

  1. Eles são invisíveis no código fonte. Observando um bloco de código, incluindo funções que podem ou não gerar exceções, não há como ver quais exceções podem ser lançadas e de onde. Isso significa que mesmo uma inspeção cuidadosa do código não revela possíveis erros.

  2. Eles criam muitos pontos de saída possíveis para uma função. Para escrever o código correto, você realmente precisa pensar em todos os caminhos de código possíveis através de sua função. Toda vez que você chama uma função que pode gerar uma exceção e não capturá-la no local, você cria oportunidades para erros surpreendentes causados ​​por funções que terminaram abruptamente, deixando os dados em um estado inconsistente ou outros caminhos de código que você não identificou pense sobre.

http://www.joelonsoftware.com/items/2003/10/13.html


Aleluia ... Ótima referência. Eu tive uma aversão com exceções exatamente por esse motivo, mas aparentemente é a maneira preferida de fazer as coisas hoje em dia. Ótimo para ver um artigo agradável que dá uma outra visão
Toad

11
+1 porque as exceções têm um risco muito alto de falhar tarde, ou seja, quando o jogo já foi lançado.
martinpi

Concordo plenamente com o ponto número 2. Não lutarei contra idiomas que usam exceções por toda parte, mas também não os considero a maneira 'certa' de lidar com condições de erro.
Kylotan

5

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.


Em qual console ele não é suportado?
David Young

Estou tentando ser tímido por causa dos NDAs, mesmo que isso tenha sido revelado mais especificamente no SO antes.
Dan Olson

2

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).


11
Você está certo, mas infelizmente, em vez de usar o tratamento alternativo de erros, a maioria dos programadores de jogos apenas volta a travar ou retornar NULL (o que provavelmente causará um travamento de qualquer maneira). Não criamos nenhum substituto decente.
Dezpn 15/08

Retornar NULL, ou um código de erro, é uma substituição perfeitamente razoável, desde que você verifique os códigos de retorno também.
JasonD

Sim, mas a maioria dos programadores de jogos não. Qual é o meu ponto.
Dezpn

11
É um problema da linguagem C ++ tanto quanto qualquer outra coisa - se você vir uma linha de código que diz 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.
Kylotan

11
A imposição de argumentos de erro não é mais um problema. O código moderno que não usa exceções depende de uma Errorclasse 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.
Samaursa

1

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.


0

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::vectora 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 throwem 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 Tpoderia ser lançada. Mesmo comparando um Tcom 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 glGetErrorpara 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_allocporque 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 noexceptfunções realmente causaram um erro do compilador se tentassem invocar qualquer funcionalidade que pudesse ser lançada em vez de apenas chamar std::terminateuma exceção. Isso é quase inútil (poderia chamar isso em icantexceptvez de noexcept). Seria muito mais fácil garantir que todos os destruidores sejam protegidos contra exceções, por exemplo, senoexceptrealmente 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 noexceptfunções (que todos os destruidores devam implicitamente) sejam permitidas apenas para chamar noexceptfunções / operadores da mesma forma e cometer um erro do compilador a partir de qualquer um deles.


-2

Na minha opinião, existem duas razões principais para não ser tradicionalmente usada no desenvolvimento de jogos:

  • A maioria dos programadores de jogos são graduados, que aprenderam sobre Java e Haskell, ou programadores C, que não têm nenhuma experiência com exceções. Portanto, eles têm muito medo deles e não sabem como usá-los corretamente.
  • O desenrolar da pilha quando uma exceção é lançada tem implicações no desempenho (embora isso seja uma espécie de sabedoria recebida e algumas referências sejam ótimas).

3
Programadores Java sem saber usar exceções? Java é baseado no tratamento de exceções. Até os programadores Java mais básicos entendem os blocos Try, Catch, Finalmente. Você realmente não pode programar em Java sem lançar exceções ou manipulá-las.
David Young

4
É mais do que empilhar. Exceções adicionam sobrecarga a cada chamada de função. codeproject.com/KB/cpp/exceptionhandler.aspx
Jonathan Fischoff

@ David David Eu estou falando mais sobre os garantes fortes / fracos, e o lado mais complexo do uso de exceções, no qual os graduados da universidade podem não ter experiência. Para complementar, muitas universidades estão muito mais interessadas em como você estrutura seu POO para coloque menos ênfase em aprender a usar as exceções Java corretamente. Mas você está certo, eu não estava claro.
tenpn

Além disso, talvez "medo" seja a palavra errada. :)
tenpn

3
Eu diria que eles geralmente são o oposto de "medo" - se você não possui uma regra estrita de "sem exceções, sem exceções", os graduados em Java mais recentes acabam lançando exceções com mais frequência do que retornando, assim como os programadores iniciantes em C use goto e break em vez de escrever bons invariantes de loop.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.