Cada vez que considero hacer que mi código sea totalmente seguro para las excepciones, justifico no hacerlo porque sería muy lento. Considere este fragmento relativamente simple:
Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;
Para implementar una garantía de excepción básica, podría usar un puntero de alcance en las new
llamadas. Esto evitaría pérdidas de memoria si alguna de las llamadas arrojara una excepción.
Sin embargo, digamos que quiero implementar una garantía de excepción fuerte. Al menos, yo tendría que poner en práctica un puntero compartida por mis contenedores (no estoy usando Boost), un nothrow Entity::Swap
para añadir los componentes de forma atómica, y algún tipo de lenguaje para añadir atómicamente tanto a la Vector
y Map
. Implementarlos no solo llevaría mucho tiempo, sino que serían caros, ya que implica mucha más copia que la solución insegura de excepción.
En última instancia, me parece que el tiempo dedicado a hacer todo eso no estaría justificado solo para que la CreateEntity
función simple sea totalmente segura. Probablemente solo quiero que el juego muestre un error y se cierre en ese punto de todos modos.
¿Hasta dónde llevas esto en tus propios proyectos de juego? ¿Es generalmente aceptable escribir código inseguro de excepción para un programa que simplemente puede bloquearse cuando hay una excepción?
Respuestas:
Si va a utilizar excepciones (lo que no quiere decir que deba hacerlo, hay ventajas y desventajas de ese enfoque que están fuera del alcance específico de esta pregunta), debe escribir correctamente el código seguro de excepción.
El código que no usa excepciones y explícitamente no es seguro para excepciones es mejor que el código que las usa, pero está a medias de su seguridad de excepción. Si tiene el último caso, tiene toda una clase de errores y fallas que pueden ocurrir cuando ocurre una excepción de la que probablemente no pueda recuperarse, lo que hace que uno de los beneficios de las excepciones sea completamente nulo. Incluso si es una clase de falla relativamente rara, aún es posible.
Esto no quiere decir que si usa excepciones, todo el código debe proporcionar la garantía de excepción fuerte, solo que cada parte del código debe proporcionar una garantía de algún tipo (ninguno, básico, fuerte) para que en el consumo de ese código usted sabe qué garantías puede brindarle el consumidor. Generalmente, cuanto más bajo sea el componente en cuestión, más fuerte debería ser la garantía.
Si nunca va a proporcionar una garantía sólida o nunca va a adoptar realmente los paradigmas de manejo de excepciones y todo el trabajo adicional y las desventajas que eso implica, tampoco obtendrá todas las ventajas que implica y es posible que tenga una tiempo solo para evitar excepciones por completo.
fuente
Mi regla general: si necesita usar excepciones, use excepciones. Si no necesita usar excepciones, no use excepciones. Si no sabe si necesita o no usar excepciones, no necesita usar excepciones.
Hay excepciones para ser su última línea de defensa absoluta en aplicaciones de misión crítica siempre activas. Si está escribiendo un software de control de tráfico aéreo que nunca puede fallar, use excepciones. Si está escribiendo un código de control para una planta de energía nuclear, use excepciones.
Al desarrollar un juego, por otro lado, es mucho mejor seguir el mantra: Choca temprano, choca ruidosamente. Si hay un problema, realmente desea saberlo lo antes posible; nunca desea que los errores se "manejen" de manera invisible, ya que esto a menudo ocultará la causa real de los comportamientos incorrectos posteriores y hará que la depuración sea mucho más difícil de lo necesario.
fuente
El problema con las excepciones es que tienes que lidiar con ellas de todos modos. Si obtiene una excepción porque no tiene memoria, es posible que de todos modos no pueda manejar ese error. También podría volcar todo el juego en 1 controlador de excepción gigante que muestra un cuadro de error.
Para el desarrollo del juego, prefiero tratar de encontrar formas de hacer que mi código continúe gentilmente con fallas cuando sea posible.
Por ejemplo, codificaré una malla de ERROR especial en mi juego. Es un círculo rojo giratorio con una X blanca y un borde blanco. Dependiendo del juego, uno invisible podría ser mejor. Tiene una instancia singleton inicializada en el inicio. Como está integrado en el código en lugar de utilizar archivos externos, no debería ser posible fallar.
En el caso de que falle una llamada normal de loadMesh en lugar de devolver algún error y estrellarse en el escritorio, registrará silenciosamente el error en la consola / archivo de registro y devolverá la malla de error codificada. Descubrí que Skyrim realmente hace lo mismo cuando un mod arruinó las cosas. Existe una buena posibilidad de que el jugador ni siquiera vea la malla de error (a menos que haya algún problema importante y muchos de ellos sean similares).
También hay un sombreador alternativo. Uno que realiza operaciones básicas de matriz de vértices, pero si no hay una matriz uniforme por alguna razón, aún debería hacer la vista ortogonal. No tiene sombreado / iluminación / materiales adecuados (aunque tiene un indicador color_overide para que pueda usarlo con mi malla de error).
El único problema posible es si la versión de malla de error podría romper el juego de alguna manera de forma permanente. Por ejemplo, asegúrese de que no aplique la física, ya que si el jugador carga un área, la patada de error en la malla podría caer al suelo y, si esta vez vuelve a cargar el juego con la malla correcta, si es más grande, podría ser incrustado en el suelo. Eso no debería ser demasiado difícil ya que probablemente vas a almacenar tus mallas físicas con las gráficas. Las secuencias de comandos también pueden ser un problema. Con algunas cosas solo tendrás que morir duro. No estoy realmente seguro de qué uso tendría un 'nivel' de retroceso (aunque podría hacer una pequeña habitación con un letrero que indicara que hubo un error, solo asegúrese de que el guardado esté desactivado ya que no desea que el jugador se quede atrapado en él).
En el caso de 'nuevo', para el desarrollo de juegos podría ser mejor considerar el uso de algún tipo de fábrica. Puede brindarle otras ventajas, como preasignar objetos antes de que realmente los necesite y / o hacerlos a granel. También hace que sea más fácil hacer cosas como un índice global de objetos que puede usar para la gestión de la memoria, encontrar cosas por una identificación (aunque también puede hacerlo en los constructores). Y, por supuesto, también puede manejar los errores de asignación allí.
fuente
Lo importante de las fallas en las comprobaciones de tiempo de ejecución y los bloqueos es la posibilidad de que un pirata informático use un bloqueo para hacerse cargo del proceso y quizás posteriormente de la máquina.
Ahora, un pirata informático no puede explotar un bloqueo real, tan pronto como ocurre el bloqueo, el proceso es inútil e inofensivo. Si simplemente lee desde un puntero nulo, se trata de un bloqueo limpio, comete un error y el proceso muere instantáneamente.
El peligro surge cuando ejecuta un comando que no es técnicamente ilegal, pero que no era lo que pretendía hacer, entonces un pirata informático podría dirigir esa acción utilizando datos cuidadosamente elaborados que de otro modo deberían ser seguros.
Una excepción no detectada en realidad no es un bloqueo real, es solo una forma de terminar el programa. Las excepciones solo ocurren porque alguien escribió una biblioteca que específicamente arroja una excepción dadas ciertas circunstancias. Por lo general, para evitar entrar en una situación inesperada, que podría ser una oportunidad para un hacker.
En realidad, el movimiento peligroso es atrapar excepciones en lugar de dejarlas escapar, eso no quiere decir que nunca debas hacerlo, pero asegúrate de atrapar solo a aquellos que sabes que puedes manejar y dejar que el resto se escape. De lo contrario, corre el riesgo de deshacer la medida de seguridad cuidadosamente puesta en su biblioteca.
Concéntrese en los errores que no necesariamente arrojan excepciones, como escribir más allá del final de una matriz. ¿Su programa por casualidad no podría hacer eso?
fuente
Debe ver sus excepciones y las condiciones que pueden desencadenarlas. Una gran cantidad de tiempo, un gran porcentaje de lo que la gente usa excepciones puede evitarse mediante verificaciones adecuadas durante el ciclo de desarrollo / depuración / prueba. Use afirmaciones, pase referencias, siempre inicialice variables locales en la declaración, memset-zero todas las asignaciones, NULL después de liberar, etc., y una gran cantidad de casos de excepción teórica simplemente desaparecen.
Considere: si algo falla y puede ser un candidato para lanzar una excepción, ¿cuál es el impacto? ¿Cuánto importa? Si falla una asignación de memoria en un juego (para tomar su ejemplo original), generalmente tiene un problema mayor en sus manos que la forma en que maneja el caso de falla, y manejar el caso de falla por medio de una excepción no hará que ese problema sea mayor vete. Peor: en realidad, puede ocultar el hecho de que el problema más grande está ahí. ¿Quieres eso? Yo se que no. Sé que si fallo una asignación de memoria, quiero bloquearme y quemar horriblemente, y hacerlo lo más cerca posible del punto de falla, para poder ingresar al depurador, averiguar qué está sucediendo y solucionarlo correctamente.
fuente
No estoy seguro de si es apropiado citar a alguien más en SE, pero creo que ninguna de las otras respuestas realmente tocó esto.
Citando a Jason Gregory de su libro Game Engine Architecture . (¡GRAN LIBRO!)
"El manejo de excepciones estructuradas (SEH) agrega mucha sobrecarga al programa. Cada marco de pila debe aumentarse para contener información adicional requerida por el proceso de desenrollado de pila. Además, el desenrollado de pila suele ser muy lento, del orden de dos a tres veces más costoso que simplemente regresar de la función. Además, si incluso una función en su programa (o biblioteca) usa SEH, todo su programa debe usar SEH. El compilador no puede saber qué funciones podrían estar por encima de usted en la pila de llamadas cuando lanzas una excepción ".
Dicho esto, mi motor que estoy construyendo no usa excepciones. Esto se debe al costo potencial de rendimiento mencionado anteriormente y al hecho de que para todos mis propósitos los códigos de retorno de error funcionan bien. El único momento en el que realmente necesito buscar fallas es en inicializar y cargar recursos y, en esos casos, devolver un valor booleano que indica que el éxito funciona bien.
fuente
Siempre hago que la excepción de mi código sea segura, simplemente porque hace que sea más fácil saber dónde surgen los problemas, ya que puedo registrar algo cada vez que se genera o detecta una excepción.
fuente