Te puedo decir cómo lo hago. Tengo un juego complejo de aproximadamente un cuarto de millón de líneas de código (kronosaurio / Trascendencia) y utilicé algunas técnicas diferentes para evitar pérdidas de memoria:
- La técnica más común es confiar en destructores . Asignar memoria en un constructor; desasignar en un destructor. Luego, asegúrese de manejar copiar y asignar semántica. Esto es particularmente útil si regresa desde el medio de una función (particularmente con excepciones).
- Si usa punteros para estructuras asignadas, asegúrese de inicializarlas (a NULL) en un constructor . Cuando establezca el puntero, verifique para asegurarse de que sea NULL. Si no, está apuntando a alguna memoria asignada. O necesita liberarlo antes de configurar el puntero o tal vez tiene un error (si no esperaba que fuera NULL).
- En equipos más grandes, uno comienza a tener problemas porque la semántica de sistemas particulares no siempre es clara. Por ejemplo, cuando obtienes un puntero de un subsistema, ¿se supone que debes liberarlo o no? Estas reglas son diferentes para cada subsistema y es fácil perder la noción. Ayuda a establecer algunas convenciones para ayudar a las personas a recordar. Por ejemplo, si una función devuelve un puntero que necesita liberar, debe tener la palabra “alloc” (p. Ej., “AllocTexture”). De lo contrario, debería usar la palabra “get” (por ejemplo, “GetTexture”). Este tipo de convenciones simples ahorran mucho tiempo.
- A veces no puede confiar en los destructores porque no puede controlar la vida útil. Por ejemplo, puede compartir una estructura asignada entre sistemas y nunca estar seguro de si alguien más todavía tiene un puntero. En ese caso, debe usar recuentos de ref . Simplemente aumente un recuento de referencias cada vez que distribuya la estructura y disminuya al liberar. Cuando el recuento es 0, puedes liberar. Lo mejor de todo es que puede ajustar sus referencias a la estructura en un objeto. Luego puede usar la regla # 1 para asegurarse de que sus referencias sean correctas: agregue una referencia en un constructor; desreferencia en un destructor. [Este es el patrón de puntero inteligente .]
- Otra técnica es tener una clase singleton que gestiona / almacena en caché un recurso. Las personas que llaman siempre pasan por la clase singleton para acceder al recurso. Por ejemplo, imagina un singleton que asigna todas las texturas utilizadas en el juego. Cuando un objeto necesita una textura (para pintar), le pide al subsistema un puntero. Como todos siempre pasan por el singleton, nadie se aferra a ningún puntero. El singleton es el único código que debe preocuparse por la asignación / liberación. Esto también le permite implementar la semántica de caché.
- Por último, la mejor defensa contra las pérdidas de memoria es la misma que la defensa contra los errores en general. Asegúrese de que sus contratos / interfaces sean claros y estén bien definidos. Asegúrese de tener pautas consistentes y comente con firmeza. Y, por último, asegúrese de tener grandes personas en su equipo comprometidas con la excelencia.