Estoy trabajando en mi propio motor de juego, y actualmente estoy diseñando mis gerentes. He leído que para la administración de memoria, el uso Init()
y las CleanUp()
funciones son mejores que el uso de constructores y destructores.
He estado buscando ejemplos de código C ++, para ver cómo funcionan esas funciones y cómo puedo implementarlas en mi motor. ¿Cómo funciona Init()
y CleanUp()
trabajo, y cómo puedo implementarlas en mi motor?
Respuestas:
Es bastante simple, en realidad:
En lugar de tener un Constructor que hace tu configuración,
... haga que su constructor haga poco o nada, y escriba un método llamado
.init
o.initialize
, que haría lo que su constructor haría normalmente.Entonces, en lugar de simplemente decir:
Se puede ir:
El beneficio es que ahora puede usar la inyección de dependencia / inversión de control más fácilmente en sus sistemas.
En lugar de decir
Se puede construir el soldado, le dan un método Equip, donde se entregue él un arma, y luego llamar a todo el resto de las funciones constructoras.
Así que ahora, en lugar de subclasificar enemigos donde un soldado tiene una pistola y otro tiene un rifle y otro tiene una escopeta, y esa es la única diferencia, solo puede decir:
El mismo trato con la destrucción. Si tiene necesidades especiales (eliminar oyentes de eventos, eliminar instancias de matrices / cualquier estructura con la que esté trabajando, etc.), debería llamarlas manualmente, para que sepa exactamente cuándo y dónde en el programa que estaba sucediendo.
EDITAR
Como Kryotan ha señalado, a continuación, esto responde al "Cómo" de la publicación original , pero en realidad no hace un buen trabajo de "Por qué".
Como probablemente pueda ver en la respuesta anterior, puede que no haya mucha diferencia entre:
y escribiendo
mientras que solo tiene una función constructora más grande.
Hay un argumento para los objetos que tienen 15 o 20 condiciones previas, lo que haría que un constructor sea muy, muy difícil de trabajar, y haría que las cosas fueran más fáciles de ver y recordar, sacando esas cosas a la interfaz , para que pueda ver cómo funciona la instanciación, un nivel más alto.
La configuración opcional de objetos es una extensión natural de esto; opcionalmente, establecer valores en la interfaz, antes de ejecutar el objeto.
JS tiene algunos atajos geniales para esta idea, que parecen fuera de lugar en lenguajes tipo C de tipo más fuerte.
Dicho esto, lo más probable es que, si está lidiando con una lista de argumentos tan larga en su constructor, que su objeto es demasiado grande y hace demasiado, como es. Una vez más, esto es algo de preferencia personal, y hay excepciones a lo largo y ancho, pero si está pasando 20 cosas en un objeto, es muy probable que encuentre una manera de hacer que ese objeto haga menos, haciendo objetos más pequeños. .
Una razón más pertinente, y que es ampliamente aplicable, sería que la inicialización de un objeto se basa en datos asincrónicos, que actualmente no tiene.
Sabe que necesita el objeto, por lo que lo va a crear de todos modos, pero para que funcione correctamente, necesita datos del servidor o de otro archivo que ahora necesita cargar.
Una vez más, ya sea que esté pasando los datos necesarios a un gigantesco init, o construyendo una interfaz, no es realmente importante para el concepto, tanto como es importante para la interfaz de su objeto y el diseño de su sistema ...
Pero en términos de construir el objeto, podrías hacer algo como esto:
async_loader
podría pasar un nombre de archivo, o un nombre de recurso o lo que sea, cargar ese recurso; tal vez carga archivos de sonido o datos de imagen, o tal vez carga estadísticas de caracteres guardadas ...... y luego volvería a alimentar esos datos
obj_w_async_dependencies.init(result);
.Este tipo de dinámica se encuentra con frecuencia en las aplicaciones web.
No necesariamente en la construcción de un objeto, para aplicaciones de nivel superior: por ejemplo, las galerías pueden cargarse e inicializarse de inmediato, y luego mostrar fotos a medida que se transmiten, eso no es realmente una inicialización asincrónica, pero donde se ve con más frecuencia sería en bibliotecas de JavaScript.
Un módulo puede depender de otro, por lo que la inicialización de ese módulo puede diferirse hasta que se complete la carga de los dependientes.
En términos de instancias específicas de este juego, considere una
Game
clase real .¿Por qué no podemos llamar
.start
o.run
en el constructor?Deben cargarse recursos: el resto de todo se ha definido y está listo, pero si intentamos ejecutar el juego sin una conexión de base de datos, o sin texturas, modelos, sonidos o niveles, no será un juego particularmente interesante ...
... entonces, ¿cuál es la diferencia entre lo que vemos de un típico
Game
, excepto que le damos a su método "seguir adelante" un nombre que es más interesante que.init
(o por el contrario, separa la inicialización aún más, para separar la carga, configurar las cosas que se han cargado y ejecutar el programa cuando todo se ha configurado).fuente
.init
, tal vez no, pero probablemente. Ergo, caso válido.Cualquier cosa que leas que diga Init y CleanUp es mejor, también debería haberte dicho por qué. No vale la pena leer los artículos que no justifican sus afirmaciones.
Tener funciones de inicialización y apagado separadas puede facilitar la configuración y destrucción de sistemas porque puede elegir en qué orden llamarlos, mientras que los constructores se llaman exactamente cuando se crea el objeto y los destructores cuando se destruye el objeto. Cuando tiene dependencias complejas entre 2 objetos, a menudo necesita que ambos existan antes de que se configuren, pero a menudo esto es un signo de un diseño deficiente en otros lugares.
Algunos idiomas no tienen destructores en los que pueda confiar, ya que el conteo de referencias y la recolección de basura hacen que sea más difícil saber cuándo se destruirá el objeto. En estos idiomas casi siempre necesita un método de apagado / limpieza, y a algunos les gusta agregar el método init para la simetría.
fuente
Creo que la mejor razón es: permitir la agrupación.
si tiene Init y CleanUp, puede, cuando se mata un objeto, simplemente llame a CleanUp y empuje el objeto a una pila de objetos del mismo tipo: un 'grupo'.
Luego, cada vez que necesite un nuevo objeto, puede hacer estallar un objeto del grupo O, si el grupo está vacío -demasiado malo-, debe crear uno nuevo. Entonces llamas a Init en este objeto.
Una buena estrategia es llenar previamente el grupo antes de que el juego comience con un "buen" número de objetos, para que nunca tenga que crear ningún objeto agrupado durante el juego.
Si, por otro lado, usa 'nuevo', y simplemente deja de hacer referencia a un objeto cuando no le sirve, crea basura que debe ser recogida en algún momento. Este recuerdo es especialmente malo para lenguajes de un solo hilo como Javascript, donde el recolector de basura detiene todo el código cuando evalúa que necesita recordar la memoria de los objetos que ya no están en uso. El juego se cuelga durante unos pocos milisegundos, y la experiencia de juego se echa a perder.
- Ya entendiste: si agrupas todos tus objetos, no se produce ningún recuerdo y, por lo tanto, no se ralentiza al azar.
También es mucho más rápido llamar a init en un objeto que viene del grupo que asignar memoria + init a un nuevo objeto.
Pero la mejora de la velocidad tiene menos importancia, ya que con frecuencia la creación de objetos no es un cuello de botella en el rendimiento ... Con algunas excepciones, como juegos frenéticos, motores de partículas o motores físicos que usan intensivamente vectores 2D / 3D para sus cálculos. Aquí, tanto la velocidad como la creación de basura se mejoran enormemente mediante el uso de un grupo.
Rq: es posible que no necesite tener un método de limpieza para sus objetos agrupados si Init () restablece todo.
Editar: responder a esta publicación me motivó a finalizar un pequeño artículo que hice sobre la agrupación en Javascript .
Puede encontrarlo aquí si está interesado:
http://gamealchemist.wordpress.com/
fuente
Su pregunta se invierte ... Históricamente hablando, la pregunta más pertinente es:
¿Por qué se combinan construcción + inicialización , es decir, por qué no hacemos estos pasos por separado? ¿Seguramente esto va en contra de SoC ?
Para C ++, la intención de RAII es que la adquisición y liberación de recursos se vincule directamente con la vida útil del objeto, con la esperanza de que esto asegure la liberación de recursos. ¿Lo hace? Parcialmente. Se cumple al 100% en el contexto de variables automáticas o basadas en pila, donde dejar el ámbito asociado llama automáticamente a los destructores / libera estas variables (de ahí el calificador
automatic
). Sin embargo, para las variables de montón, este patrón muy útil se rompe tristemente, ya que aún se ve obligado a llamar explícitamentedelete
para ejecutar el destructor, y si olvida hacerlo, lo morderá lo que RAII intenta resolver; en el contexto de las variables asignadas en el montón, entonces, C ++ proporciona un beneficio limitado sobre C (delete
vsfree()
) mientras se combina la construcción con la inicialización, lo que impacta negativamente en términos de lo siguiente:Se recomienda encarecidamente construir un sistema de objetos para juegos / simulaciones en C, ya que arrojará mucha luz sobre las limitaciones de RAII y otros patrones centrados en OO a través de una comprensión más profunda de los supuestos que C ++ y los lenguajes clásicos de OO posteriores (recuerde que C ++ comenzó como un sistema OO integrado en C).
fuente