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.