¿Lanzando excepciones en las DLL del juego C ++? Pros y contras

8

¿Cuáles son las ventajas y desventajas de utilizar Excepciones en C ++ en relación con el desarrollo del juego?

La guía de estilo de Google dice que no usan Excepciones por una variedad de razones. ¿Son las mismas razones pertinentes para el desarrollo del juego?

No utilizamos excepciones de C ++ ... - google-styleguide.googlecode.com

Algunas cuestiones para pensar.

  • Cómo se relaciona con el desarrollo de bibliotecas utilizadas a través de múltiples proyectos.
  • ¿Cómo afecta a las pruebas unitarias, las pruebas de integración, etc.?
  • ¿Qué hace al usar bibliotecas de terceros?
David Young
fuente
3
Curiosamente, nadie tenía ningún profesional
David Young

Respuestas:

7

También hay algunas consecuencias desagradables por usar excepciones en C ++ si no conoce todos los detalles de cómo funcionan. Dado que muchos juegos están escritos en C ++, esto puede ser un factor.

Por ejemplo, en C ++, lanzar una excepción en un destructor puede tener serias consecuencias. Puede causar todo tipo de comportamientos indefinidos y accidentes, ya que puede quedar atrapado en una situación en la que tiene más de una excepción activa en vuelo. (Consulte el Artículo # 8 de C ++ efectivo para obtener más información al respecto).

Bob Somers
fuente
44
+1 para C ++ efectivo. Leyendo la tercera edición mientras hablamos. Las excepciones están bien conmigo, siempre y cuando seas muy estricto sobre dónde las usas, dónde las atrapas y puedes garantizar que cualquier código de terceros haga lo mismo.
Anthony
9

Joel sobre las opiniones de Software sobre Excepciones.

Vista interesante que actualmente no está en ninguna de las respuestas,

El razonamiento es que considero que las excepciones no son mejores que "goto's", consideradas dañinas desde la década de 1960, ya que crean un salto brusco de un punto de código a otro. De hecho, son significativamente peores que los de Goto:

  1. Son invisibles en el código fuente. Al observar un bloque de código, incluidas las funciones que pueden o no arrojar excepciones, no hay forma de ver qué excepciones se pueden lanzar y desde dónde. Esto significa que incluso una inspección cuidadosa del código no revela posibles errores.

  2. Crean demasiados puntos de salida posibles para una función. Para escribir el código correcto, realmente tiene que pensar en cada ruta de código posible a través de su función. Cada vez que llama a una función que puede generar una excepción y no la detecta en el acto, crea oportunidades para errores sorpresa causados ​​por funciones que terminaron abruptamente, dejando datos en un estado inconsistente u otras rutas de código que usted no pensar en.

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

David Young
fuente
Aleluya ... Gran referencia. He tenido una aversión con excepciones exactamente por esta razón, pero aparentemente es la forma preferida de hacer las cosas hoy en día. Genial para ver un buen artículo que da otra vista
Sapo
1
+1 porque las excepciones tienen un riesgo demasiado alto para fallar tarde, es decir, cuando el juego ya está disponible.
martinpi
Estoy completamente de acuerdo con el punto número 2. No lucharé contra los idiomas que usan excepciones en todo momento, pero tampoco los considero la forma "correcta" de manejar las condiciones de error.
Kylotan
5

Las excepciones no son compatibles y, por lo tanto, no se recomiendan en al menos un entorno de desarrollo de consola moderno.

La mayoría de los desarrolladores de consolas que conozco prefieren no usarlos de todos modos debido a la sobrecarga adicional en el ejecutable y la filosofía de que simplemente no son necesarios. RTTI se ve de la misma manera.

Dan Olson
fuente
¿En qué consola no es compatible?
David Young
Estoy tratando de ser tímido debido a los NDA, a pesar de que se ha revelado más específicamente en SO antes.
Dan Olson
2

De todos los proyectos en los que trabajé en juegos, ninguno de ellos utilizó excepciones. La sobrecarga de llamadas de función es la razón principal. Como se mencionó anteriormente, como RTTI, en la mayoría de los estudios, no es un tema de discusión. No porque la mayoría de los programadores provengan de un entorno académico / Java, porque francamente, la mayoría no.
En realidad, no afecta a las pruebas unitarias, ya que solo afirma las condiciones. Para las bibliotecas, es mejor sin excepciones, ya que lo impondrá a todos los que lo usen.
Si bien hace que algunas situaciones sean un poco más difíciles de manejar, también (imo) te obliga a tener una configuración más controlada, ya que las excepciones pueden conducir al abuso. La tolerancia a errores es buena, pero no si conduce a una codificación descuidada.
Eso sí, esto proviene de alguien que nunca usó el manejo de excepciones (bueno, está bien, una o dos veces en herramientas, no lo hizo por mí, supongo que es con lo que creciste).

Kaj
fuente
1
Tienes razón, pero desafortunadamente en lugar de usar un manejo de errores alternativo, la mayoría de los programadores de juegos simplemente vuelven a fallar o devolver NULL (lo que probablemente conducirá a un bloqueo de todos modos). No hemos encontrado ningún reemplazo decente.
Tenpn
Devolver NULL, o un código de error, es un reemplazo perfectamente razonable siempre que verifique también los códigos de devolución.
JasonD
Sí, pero la mayoría de los programadores de juegos no. Cual es mi punto.
Tenpn
1
Es un problema del lenguaje C ++ más que nada: si ve una línea de código que dice function();que no sabe instantáneamente si se ignora o no un código de error. Presumiblemente, esta es la razón por la cual los lenguajes que le permiten ignorar un valor de retorno intentan forzarlo a preferir excepciones.
Kylotan
1
La aplicación de argumentos de error ya no es un problema. El código moderno que no utiliza excepciones se basa en una Errorclase que es liviana y, si se ignora, se genera una aserción. Por lo tanto, no se puede ignorar más de lo que se puede ignorar una excepción.
Samaursa
1

Lo más importante para mí como desarrollador de juegos profesional es que las excepciones deben ser escritas por personas que entiendan cómo funcionan las excepciones (que hay pocas en la industria de los juegos) depuradas por ellos también, y el código subyacente que las ejecuta (que es un desorden ramificado de todos modos y matará a su procesador en orden) tuvo que ser escrito por personas que proporcionan su compilador / enlazador. El problema con eso es que solo tienen una cantidad finita de recursos, por lo que se concentran en escribir mejores optimizaciones y correcciones para el código que los desarrolladores de juegos usan ... lo que generalmente no es una excepción. Si tienes un error de excepción en el hardware moderno con nuevos compiladores, ¿a quién vas a llamar? su experto en excepciones que cree que todo está bien con el código, o el proveedor que dice, sí, pronto, nosotros '

No hay nada inherentemente malo en las excepciones, solo que no son buenas porque la realidad se interpone en el camino de la teoría.

Richard Fabian
fuente
0

Para mí, el manejo de excepciones puede ser excelente si todo se ajusta a RAII y está reservando excepciones para rutas verdaderamente excepcionales (por ejemplo, un archivo corrupto que se está leyendo) y nunca necesita cruzar los límites del módulo y todo su equipo está a bordo con ellos. ......

EH de costo cero

La mayoría de los compiladores en la actualidad implementan un manejo de excepciones de costo cero que puede hacerlo incluso más barato que la ramificación manual en condiciones de error en las rutas de ejecución regulares, aunque a cambio hace que las rutas excepcionales sean enormemente caras (también hay algo de código hinchado, aunque probablemente no más) que si manejas a fondo todos los errores posibles manualmente Sin embargo, el hecho de que el lanzamiento sea enormemente costoso no debería importar si las excepciones se usan de la manera en que están destinadas en C ++ (para circunstancias verdaderamente excepcionales que no deberían ocurrir en condiciones normales).

Inversión de efectos secundarios

Dicho esto, hacer que todo se ajuste a RAII es mucho más fácil decirlo que hacerlo. Es fácil para los recursos locales almacenados en una función envolverlos en objetos C ++ con destructores que se limpian solos, pero no es tan fácil escribir la lógica de deshacer / deshacer para cada efecto secundario que podría ocurrir en todo el software. Solo considere lo difícil que es escribir un contenedor como el std::vectorque es la secuencia de acceso aleatorio más simple para que sea perfectamente seguro para las excepciones. Ahora multiplique esa dificultad en todas las estructuras de datos de un software completo a gran escala.

Como ejemplo básico, considere una excepción encontrada en el proceso de insertar elementos en un gráfico de escena. Para recuperarse adecuadamente de esa excepción, es posible que deba deshacer esos cambios en el gráfico de escena y restaurar el sistema a un estado como si la operación nunca hubiera ocurrido. Eso significa eliminar automáticamente a los niños insertados en el gráfico de la escena al encontrar una excepción, tal vez desde un guardia de alcance.

Hacer esto a fondo en un software que causa muchos efectos secundarios y se ocupa de una gran cantidad de estado persistente es mucho más fácil decirlo que hacerlo. A menudo creo que la solución pragmática es no molestarse con el interés de enviar las cosas. Sin embargo, con el tipo correcto de base de código que tiene algún tipo de sistema central de deshacer y quizás estructuras de datos persistentes y el número mínimo de funciones que causan efectos secundarios, es posible que pueda lograr una seguridad de excepción en todos los ámbitos ... pero es mucho lo que necesita si todo lo que va a hacer es tratar de hacer que su código sea seguro en todas partes.

Hay algo prácticamente mal allí porque conceptualmente la reversión de los efectos secundarios es un problema difícil, pero se hace más difícil en C ++. En realidad, a veces es más fácil revertir los efectos secundarios en C en respuesta a un código de error que hacerlo en respuesta a una excepción en C ++. Una de las razones es porque cualquier punto dado de cualquier función podría potencialmente throwen C ++. Si está tratando de escribir un contenedor genérico que funcione con tipo T, ni siquiera puede anticipar dónde están esos puntos de salida, ya que cualquier cosa que implique Tpodría arrojarse. Incluso comparar uno Tcon otro por la igualdad podría arrojar. Su código tiene que manejar todas las posibilidades , y el número de posibilidades se multiplica exponencialmente en C ++, mientras que en C, son mucho menos numerosas.

Falta de estándares

Otro problema de manejo de excepciones es la falta de estándares centrales. Hay algunos que, con suerte, todos están de acuerdo en que los destructores nunca deberían arrojar, ya que los retrocesos nunca deberían fallar, pero eso solo cubre un caso patológico que generalmente bloquearía el software si viola la regla.

Debería haber algunas normas más sensatas como nunca lanzar desde un operador de comparación (todas las funciones que son lógicamente inmutables nunca deberían lanzar), nunca lanzar desde un ctor de movimiento, etc. Tales garantías facilitarían mucho más la escritura de códigos seguros para excepciones, pero no tenemos tales garantías. Tenemos que seguir la regla de que todo podría arrojar, si no ahora, posiblemente en el futuro.

Peor aún, las personas de diferentes orígenes lingüísticos ven las excepciones de manera muy diferente. ¡En Python, en realidad tienen esta filosofía de "salto antes de mirar" que implica desencadenar excepciones en las rutas de ejecución regulares! Cuando obtenga que dichos desarrolladores escriban código C ++, podría estar viendo excepciones lanzadas de izquierda a derecha para cosas que no son realmente excepcionales, y manejarlo todo puede ser una pesadilla si está tratando de escribir código genérico, por ejemplo

Manejo de errores

El manejo de errores tiene la desventaja de que debe verificar cada posible error que pueda ocurrir. Pero tiene una ventaja: a veces, en retrospectiva, puede verificar los errores un poco tarde, comoglGetError , para reducir significativamente los puntos de salida en una función y hacerlos explícitos. A veces puede seguir ejecutando el código un poco más antes de verificar, propagarse y recuperarse de un error, y a veces eso puede ser realmente más fácil que el manejo de excepciones (especialmente con la reversión de efectos secundarios). Pero es posible que ni siquiera te molestes tanto con los errores en un juego, tal vez solo apagues el juego con un mensaje si encuentras cosas como archivos corruptos, errores de falta de memoria, etc.

Cómo se relaciona con el desarrollo de bibliotecas utilizadas a través de múltiples proyectos.

Una parte desagradable de las excepciones en contextos DLL es que no puede lanzar excepciones de un módulo a otro de manera segura a menos que pueda garantizar que estén compiladas por el mismo compilador, use la misma configuración, etc. Entonces, si está escribiendo como una arquitectura de complemento destinado a personas de todo tipo de compiladores y posiblemente incluso de diferentes lenguajes como Lua, C, C #, Java, etc., a menudo las excepciones comienzan a convertirse en una molestia importante ya que hay que tragar todas las excepciones y traducirlas en error códigos de todos modos en todo el lugar.

¿Qué hace al usar bibliotecas de terceros?

Si son dylibs, entonces no pueden lanzar excepciones de manera segura por las razones mencionadas anteriormente. Tendrían que usar códigos de error, lo que también significa que, usando la biblioteca, tendría que verificar constantemente los códigos de error. Podrían envolver su dylib con una biblioteca de envoltura C ++ estáticamente vinculada que usted mismo construye, que traduce los códigos de error del dylib en excepciones lanzadas (básicamente arrojando desde su binario a su binario). En general, creo que la mayoría de las librerías de terceros ni siquiera deberían molestarse y limitarse a los códigos de error si usan dylibs / libs compartidas.

¿Cómo afecta a las pruebas unitarias, las pruebas de integración, etc.?

Por lo general, no encuentro equipos que prueben tanto las rutas de error / excepcionales de su código. Solía ​​hacerlo y en realidad tenía un código que podía recuperarse correctamente bad_allocporque quería hacer mi código ultra robusto, pero realmente no ayuda cuando soy el único que lo hace en un equipo. Al final dejé de molestarme. Me imagino un software de misión crítica que equipos completos podrían verificar para asegurarse de que su código se recupere de manera sólida de excepciones y errores en todos los casos posibles, pero eso es mucho tiempo para invertir. El tiempo tiene sentido en el software de misión crítica, pero quizás no en un juego.

Amor / odio

Las excepciones también son mi tipo de característica de amor / odio de C ++, una de las características más impactantes del lenguaje que hace que RAII sea más un requisito que una conveniencia, ya que cambia la forma en que tiene que escribir el código al revés, por ejemplo , C para manejar el hecho de que cualquier línea de código dada podría ser un punto de salida implícito para una función. A veces casi deseo que el idioma no tenga excepciones. Pero hay momentos en que encuentro excepciones realmente útiles, pero son un factor demasiado dominante para mí si elijo escribir un fragmento de código en C o C ++.

Serían exponencialmente más útiles para mí si el comité estándar realmente se concentrara en establecer estándares ABI que permitieran que las excepciones se lanzaran de forma segura a través de los módulos, al menos desde el código C ++ a C ++. Sería increíble si pudieras lanzar idiomas de manera segura, aunque eso es una especie de sueño imposible. La otra cosa que haría las excepciones exponencialmente más útiles para mí es si las noexceptfunciones realmente causaron un error del compilador si intentaron invocar cualquier funcionalidad que pudiera generar en lugar de simplemente invocar std::terminateuna excepción. Eso es casi inútil (también podría llamarse así en icantexceptlugar de noexcept). Sería mucho más fácil asegurarse de que todos los destructores estén a salvo de excepciones, por ejemplo, sinoexcepten realidad causó un error de compilación si el destructor hizo algo que podría arrojar. Si eso es demasiado costoso para determinar a fondo en tiempo de compilación, simplemente haga que lasnoexceptlas funciones (que todos los destructores deberían ser implícitamente) solo pueden llamar a noexceptfunciones / operadores, y convertirlo en un error del compilador desde cualquiera de ellos.


fuente
-2

Por lo que veo, hay dos razones principales por las que no se usa tradicionalmente en el desarrollo de juegos:

  • La mayoría de los programadores de juegos son graduados, que aprendieron en Java y Haskell, o programadores en C, que no tienen experiencia en excepciones. Por lo tanto, les tienen mucho miedo y no saben cómo usarlos correctamente.
  • El desbobinado de la pila cuando se produce una excepción tiene implicaciones de rendimiento (aunque esto es una especie de sabiduría recibida, y algunas referencias serían geniales).
tenpn
fuente
3
¿Los programadores de Java no saben cómo usar Excepciones? Java se basa en el manejo de excepciones. Incluso los programadores más básicos de Java entienden los bloques Try, Catch, Finalmente. Realmente no puede programar en Java sin lanzar Excepciones o manejarlas.
David Young el
44
Es más que simplemente desbobinar la pila. Las excepciones agregan gastos generales a cada llamada de función. codeproject.com/KB/cpp/exceptionhandler.aspx
Jonathan Fischoff
@David Young Estoy hablando más sobre las garantías fuertes / débiles, y el lado más complejo del uso de excepciones, que los graduados universitarios pueden no tener experiencia. Para complicarlo, muchas universidades están mucho más interesadas en cómo estructurar su OOP. Ponga menos énfasis en aprender a usar las excepciones de Java correctamente. Pero tienes razón, no estaba claro.
Tenpn
Además, tal vez "miedo" es la palabra incorrecta. :)
tenpn
3
Yo diría que a menudo son lo contrario de "miedo": si no tienes una regla estricta de "sin excepciones, sin excepciones", los graduados más recientes de Java terminarán lanzando excepciones con más frecuencia que regresando, al igual que los programadores novatos de C use goto y break en lugar de escribir buenos invariantes de bucle.