La propiedad compartida rara vez tiene sentido
Esta respuesta puede estar ligeramente fuera de la tangente, pero tengo que preguntar, ¿cuántos casos tiene sentido desde el punto de vista del usuario final compartir la propiedad ? Al menos en los dominios en los que he trabajado, prácticamente no había ninguno porque, de lo contrario, eso implicaría que el usuario no necesita simplemente eliminar algo una vez de un lugar, sino eliminarlo explícitamente de todos los propietarios relevantes antes de que el recurso esté realmente eliminado del sistema.
A menudo es una idea de ingeniería de nivel inferior para evitar que los recursos se destruyan mientras otra cosa todavía está accediendo a ella, como otro hilo. A menudo, cuando un usuario solicita cerrar / eliminar / eliminar algo del software, debe eliminarse lo antes posible (siempre que sea seguro eliminarlo), y ciertamente no debe demorarse y causar una fuga de recursos durante el tiempo que sea necesario. La aplicación se está ejecutando.
Como ejemplo, un activo de juego en un videojuego podría hacer referencia a un material de la biblioteca de materiales. Ciertamente, no queremos, por ejemplo, un bloqueo del puntero colgante si el material se elimina de la biblioteca de materiales en un hilo mientras otro hilo todavía está accediendo al material al que hace referencia el activo del juego. Pero eso no significa que tenga sentido que los activos del juego compartan la propiedad de los materiales a los que hacen referencia con la biblioteca de materiales. No queremos obligar al usuario a eliminar explícitamente el material de los activos y la biblioteca de materiales. Solo queremos asegurarnos de que los materiales no se eliminen de la biblioteca de materiales, el único propietario sensible de los materiales, hasta que otros hilos terminen de acceder al material.
Fugas de recursos
Sin embargo, trabajé con un antiguo equipo que adoptó GC para todos los componentes del software. Y aunque eso realmente ayudó a asegurarnos de que nunca se destruyeran los recursos mientras otros subprocesos todavía estaban accediendo a ellos, en su lugar, terminamos recibiendo nuestra parte de fugas de recursos .
Y estas no fueron fugas de recursos triviales de un tipo que molesta solo a los desarrolladores, como un kilobyte de memoria filtrado después de una sesión de una hora. Estas fueron fugas épicas, a menudo gigabytes de memoria durante una sesión activa, lo que condujo a informes de errores. Porque ahora, cuando se hace referencia a la propiedad de un recurso (y, por lo tanto, se comparte en propiedad) entre, por ejemplo, 8 partes diferentes del sistema, solo se necesita una para no eliminar el recurso en respuesta al usuario que solicita que se elimine por él ser filtrado y posiblemente indefinidamente.
Por lo tanto, nunca fui un gran admirador del GC o del recuento de referencias aplicado a gran escala debido a lo fácil que fue crear un software con fugas. Lo que anteriormente habría sido un accidente puntero colgante que es fácil de detectar se convierte en una fuga de recursos muy difícil de detectar que puede volar fácilmente bajo el radar de las pruebas.
Las referencias débiles pueden mitigar este problema si el idioma / la biblioteca los proporciona, pero me resultó difícil lograr que un equipo de desarrolladores de conjuntos de habilidades mixtas pueda utilizar constantemente referencias débiles siempre que sea apropiado. Y esta dificultad no solo estaba relacionada con el equipo interno, sino con todos los desarrolladores de plugins para nuestro software. También podrían hacer que el sistema pierda recursos simplemente almacenando una referencia persistente a un objeto de manera que sea difícil rastrear al complemento como el culpable, por lo que también obtuvimos la mayor parte de los informes de errores resultantes de nuestros recursos de software se filtró simplemente porque un complemento cuyo código fuente estaba fuera de nuestro control no pudo liberar referencias a esos costosos recursos.
Solución: eliminación periódica diferida
Entonces, mi solución más adelante, que apliqué a mis proyectos personales que me dieron el tipo de lo mejor que encontré de ambos mundos, fue eliminar el concepto que referencing=ownership
aún difiere la destrucción de recursos.
Como resultado, ahora cada vez que el usuario hace algo que hace que un recurso necesite ser eliminado, la API se expresa en términos de solo eliminar el recurso:
ecs->remove(component);
... que modela la lógica del usuario final de una manera muy directa. Sin embargo, el recurso (componente) no puede eliminarse de inmediato si hay otros subprocesos del sistema en su fase de procesamiento en los que podrían acceder al mismo componente al mismo tiempo.
Por lo tanto, estos subprocesos de procesamiento producen tiempo aquí y allá, lo que permite que un subproceso que se asemeja a un recolector de basura se despierte y " pare el mundo " y destruya todos los recursos que se solicitó que se eliminen mientras bloquea los subprocesos del procesamiento de esos componentes hasta que finalice . He ajustado esto para que la cantidad de trabajo que se necesita hacer aquí sea generalmente mínima y no se reduzca notablemente en las velocidades de fotogramas.
Ahora no puedo decir que este sea un método probado y probado y bien documentado, pero es algo que he estado usando durante algunos años, sin dolores de cabeza y sin pérdidas de recursos. Recomiendo explorar enfoques como este cuando es posible que su arquitectura se ajuste a este tipo de modelo de concurrencia, ya que es mucho menos pesado que el GC o el recuento de ref. Y no se arriesga a que este tipo de fugas de recursos vuelen bajo el radar de las pruebas.
El único lugar donde encontré útil el recuento de ref. O GC es para las estructuras de datos persistentes. En ese caso, es el territorio de la estructura de datos, lejos de las preocupaciones del usuario final, y allí tiene sentido que cada copia inmutable pueda compartir la propiedad de los mismos datos no modificados.