As exceções são amplamente usadas no design de mecanismos de jogos ou é mais preferível usar instruções if puras? Por exemplo, com exceções:
try {
m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
} catch ... {
// some info about error
}
e com ifs:
m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
if( m_fpsTextId == -1 ) {
// show error
}
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
if( m_cpuTextId == -1 ) {
// show error
}
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
if( m_frameTimeTextId == -1 ) {
// show error
}
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
if( m_mouseCoordTextId == -1 ) {
// show error
}
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
if( m_cameraPosTextId == -1 ) {
// show error
}
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
if( m_cameraRotTextId == -1 ) {
// show error
}
Ouvi dizer que as exceções são um pouco mais lentas que as ifs e não devo misturar exceções com o método de verificação de if. No entanto, com exceções, tenho um código mais legível do que com toneladas de ifs após cada método initialize () ou algo semelhante, embora eles às vezes sejam muito pesados para apenas um método na minha opinião. Eles estão bem para o desenvolvimento de jogos ou melhor, ficar com ifs simples?
Respostas:
Resposta curta: leia Tratamento sensível a erros 1 , Tratamento sensível a erros 2 e Tratamento sensível a erros 3 por Niklas Frykholm. Na verdade, leia todos os artigos desse blog enquanto estiver nele. Não vou dizer que concordo com tudo, mas a maioria é de ouro.
Não use exceções. Há uma infinidade de razões. Vou listar os principais.
Eles podem realmente ser mais lentos, embora isso seja bastante minimizado em compiladores mais recentes. Alguns compiladores suportam "zero exceções de sobrecarga" para caminhos de código que realmente não acionam exceção (embora isso seja um pouco mentiroso, pois ainda existem dados extras que o tratamento de exceções exige, inchando o tamanho do executável / dll). O resultado final é que sim, o uso de exceções é mais lento e, em qualquer desempenho crítico caminho de código , você deve evitá-las. Dependendo do seu compilador, tê-los ativados pode adicionar sobrecarga. Eles sempre aumentam o tamanho do código, de maneira bastante significativa em muitos casos, o que pode afetar seriamente o desempenho do hardware atual.
Exceções tornam o código muito mais frágil. Existe um gráfico infame (que infelizmente não consigo encontrar no momento) que basicamente apenas mostra em um gráfico a dificuldade de escrever código com exceção de segurança versus código com exceção de inseguro, e o primeiro é uma barra significativamente maior. Existem simplesmente algumas pequenas dicas com exceções e muitas maneiras de escrever código que parece excepcionalmente seguro, mas realmente não é. Mesmo todo o comitê do C ++ 11 brincou com esse e esqueceu de adicionar funções auxiliares importantes para o uso correto do std :: unique_ptr, e mesmo com essas funções auxiliares, é preciso digitar mais para usá-las do que não e a maioria dos programadores venceu ' nem percebe o que há de errado se não o fizerem.
Mais especificamente para a indústria de jogos, alguns compiladores / tempos de execução fornecidos pelos fornecedores dos consoles não suportam totalmente as exceções, ou mesmo nem sequer as suportam. Se seu código usa exceções, você ainda pode reescrever partes do seu código para portá-lo para novas plataformas. (Não tenho certeza se isso foi alterado nos 7 anos desde que os consoles foram lançados; simplesmente não usamos exceções, elas são desativadas nas configurações do compilador, por isso não sei se alguém com quem conversei verificou recentemente.)
A linha de pensamento geral é bastante clara: use exceções para circunstâncias excepcionais . Use-os quando o seu programa entrar em um "Eu não tenho idéia do que fazer, talvez espero que alguém o faça, então vou abrir uma exceção e ver o que acontece". Use-os quando nenhuma outra opção fizer sentido. Use-os quando não se importar se você vazar acidentalmente um pouco de memória ou falhar na limpeza de um recurso porque você estragou o uso de identificadores inteligentes adequados. Em todos os outros casos, não os use.
Em relação a códigos como o seu exemplo, você tem várias outras maneiras de corrigir o problema. Um dos mais robustos - embora não necessariamente o mais ideal em seu exemplo simples - é inclinar-se para os tipos de erros monádicos. Ou seja, createText () pode retornar um tipo de identificador personalizado em vez de um número inteiro. Esse tipo de identificador possui acessadores para atualizar ou controlar o texto. Se o identificador for colocado em um estado de erro (porque createText () falhou), outras chamadas para o identificador simplesmente falharão silenciosamente. Você também pode consultar o identificador para ver se houve algum erro e, em caso afirmativo, qual foi o último erro. Essa abordagem tem mais sobrecarga do que outras opções, mas é bastante sólida. Use-o nos casos em que você precise executar uma longa sequência de operações em algum contexto em que uma única operação possa falhar na produção, mas onde você não / não pode / venceu '
Uma alternativa para implementar a manipulação de erros monádicos é, em vez de usar objetos de manipulação personalizados, fazer com que os métodos no objeto de contexto lidem normalmente com IDs de manipulação inválidos. Por exemplo, se createText () retornar -1 quando falhar, quaisquer outras chamadas para m_statistics que usam uma dessas alças devem sair normalmente se -1 for passado.
Da mesma forma, você pode colocar o erro de impressão dentro da função que está realmente falhando. No seu exemplo, o createText () provavelmente possui muito mais informações sobre o que deu errado, portanto poderá despejar um erro mais significativo no log. Neste caso, há pouco benefício em enviar o tratamento / impressão de erros aos chamadores. Faça isso quando os chamadores precisarem personalizar o tratamento (ou usar a injeção de dependência). Observe que ter um console no jogo que pode aparecer sempre que um erro é registrado é uma boa ideia e ajuda aqui também.
A melhor opção (já apresentada na série vinculada de artigos acima) para chamadas que você não espera falhar em nenhum ambiente sadio - como o simples ato de criar blobs de texto para um sistema de estatísticas - é apenas ter a função que falhou (createText no seu exemplo) abortar. Você pode ter certeza razoável de que createText () não falhará na produção, a menos que algo seja totalmente destruído (por exemplo, o usuário excluiu os arquivos de dados da fonte ou, por algum motivo, tenha apenas 256 MB de memória etc.). Em muitos desses casos, não há nem uma coisa boa a fazer quando ocorre uma falha. Fora da memória? Talvez você nem consiga fazer uma alocação necessária para criar um bom painel da GUI para mostrar ao usuário o erro OOM. Faltando fontes? Torna difícil exibir erros para o usuário. O que está errado,
Apenas travar é totalmente bom, desde que você (a) registre o erro em um arquivo de log e (b) faça apenas em erros que não são causados por ações regulares do usuário.
Eu não diria o mesmo para muitos aplicativos de servidor, onde a disponibilidade é crítica e o monitoramento de vigilância não é suficiente, mas isso é bem diferente do desenvolvimento do cliente do jogo . Eu também evitaria o uso do C / C ++ lá, pois os recursos de tratamento de exceções de outras linguagens tendem a não morder como o C ++, pois são ambientes gerenciados e não têm todos os problemas de segurança de exceção que o C ++ possui. Quaisquer preocupações de desempenho também são atenuadas, pois os servidores tendem a se concentrar mais em paralelismo e taxa de transferência do que as garantias mínimas de latência, como os clientes do jogo. Até servidores de jogos de ação para atiradores e similares podem funcionar muito bem quando escritos em C #, por exemplo, uma vez que raramente levam o hardware ao limite, como costumam fazer os clientes de FPS.
fonte
warn_unused_result
a função em alguns compiladores, o que permite capturar código de erro não tratado.A velha sabedoria do próprio Donald Knuth é:
Imprima isso como um cartaz grande e pendure-o em qualquer lugar onde você faça uma programação séria.
Portanto, mesmo se try / catch for um pouco mais lento, eu o usaria por padrão:
O código deve ser o mais legível e compreensível possível. Você pode escrever o código uma vez, mas você vai lê-lo muitas mais vezes para depurar este código, para depuração de outro código, para entender, para a melhoria, ...
Correção sobre o desempenho. Fazer as coisas if / else corretamente não é trivial. No seu exemplo, isso é feito incorretamente, porque nenhum erro pode ser mostrado, mas vários. Você precisa usar em cascata se / then / else.
Mais fácil de usar de forma consistente: o try / catch adota um estilo em que você não precisa verificar se há erros em todas as linhas. Por outro lado: Um faltando if / else e seu poder de código destruir estragos. Obviamente, apenas em circunstâncias improdutíveis.
Então, o último ponto:
Ouvi dizer que cerca de 15 anos atrás, não ouvi isso de nenhuma fonte confiável recentemente. Compiladores podem ter melhorado ou o que for.
O ponto principal é: Não faça otimização prematura. Faça isso apenas quando puder provar por referência que o código em questão é o loop interno apertado de algum código muito usado e que o estilo de alternância melhora consideravelmente o desempenho . Este é o caso de 3%.
fonte
Já houve uma resposta realmente ótima para essa pergunta, mas gostaria de acrescentar algumas idéias sobre a legibilidade do código e a recuperação de erros no seu caso específico.
Acredito que seu código possa ter a seguinte aparência:
Sem exceções, sem ifs. O relatório de erros pode ser feito em
createText
. EcreateText
pode retornar um ID de textura padrão que não exige que você verifique o valor de retorno, para que o restante do código funcione da mesma forma.fonte