Estoy practicando el uso de objetos inmutables en C ++. Mi objetivo personal es representar un gráfico de objeto genérico (en montón) con una secuencia de gráficos inmutables.
Construir el gráfico de múltiples versiones en sí no es tan difícil. El problema es el rendimiento. El control de versiones de fuerza bruta necesita una copia completa del gráfico, y esto no era aceptable.
Traté de compartir nodos sin cambios. Pero en este caso, tengo un nuevo problema; referencias La referencia a otro objeto debe actualizarse en todo el gráfico. Esto necesita visitar todos los nodos cada vez que obtengo una nueva versión del gráfico. Y esto muta los nodos con referencias, por lo que también deben derivarse (copiando). El rendimiento no será mejor que la copia con fuerza bruta.
Por lo que puedo imaginar, no hay una forma realmente eficiente de representar la mutación del gráfico de objetos con estados inmutables. Entonces, estoy pidiendo alguna idea sobre esto.
¿Es posible representar la mutación del gráfico de objeto de manera eficiente con un estado inmutable?
fuente
Respuestas:
Lo que está buscando se llama Estructura de datos persistente . El recurso canónico para las estructuras de datos persistentes son las Estructuras de datos puramente funcionales del libro de Chris Okasaki . Las estructuras de datos persistentes han despertado interés en los últimos tiempos debido a su popularización en Clojure y Scala.
Sin embargo, por alguna extraña razón, los gráficos persistentes parecen ser ignorados en su mayoría. Tenemos listas, docenas de diferentes tipos de árboles, matrices, colas de prioridad, mapas, pero no hay gráficos.
Realmente solo encontré un artículo: Gráficos totalmente persistentes: ¿cuál elegir?
fuente
Si no considera que las conexiones entre los objetos sean parte de su recurso versionado (y podría, en cuyo caso lo siguiente probablemente no ayuda mucho), podría considerar dividir sus objetos en una parte que represente la parte de conectividad del objeto y una parte que representa el estado inmutable.
Entonces podría hacer que cada uno de los subobjetos de conectividad contenga un vector de los estados versionados. De esta forma, podría operar con un número de versión de gráfico para acceder al estado inmutable apropiado.
Para evitar tener que atravesar todo el gráfico cada vez que hay una actualización de un nodo específico, puede hacerlo de modo que si se accede a un nodo con un número de versión mayor que el número de versión actual del nodo, se use la versión actual . Si el nodo se actualiza, debe completar todas las versiones intermedias con la versión anterior, lo que le permite realizar actualizaciones diferidas en el gráfico de objetos.
Si la conectividad entre objetos es parte de su estado versionado, entonces lo anterior no funciona. Pero tal vez pueda extenderlo de la siguiente manera:
Para cada objeto en el gráfico, cree un "objeto de control". El objeto de controlador contiene la lista de estados inmutables versionados. En lugar de almacenar referencias de objeto en cualquiera de los objetos del gráfico, debería almacenar una referencia al objeto del controlador. Luego, cada referencia a los objetos sería desreferenciada a través del identificador utilizando el identificador y el número de versión de la vista del gráfico de objeto que se estaba procesando actualmente. Esto devolvería el estado inmutable correcto para el objeto. Los estados inmutables usan los controladores para referirse a otros objetos en el gráfico, por lo que siempre obtiene la fecha coherente para la versión del gráfico que desea procesar.
La misma lógica descrita anteriormente se aplica para actualizar las versiones dentro de los identificadores, lo que permite actualizaciones perezosas y localizadas.
fuente
Hay una solución publicada para este problema con una muy buena complejidad de tiempo amortizado, pero es difícil de encontrar cuando no sabes exactamente qué buscar. Puede encontrar un buen resumen en estas notas de clase (consulte la sección 2.2.3) o lea el documento original Cómo hacer que las estructuras de datos sean persistentes . Si su gráfico de objeto tiene conectividad limitada (p. Ej., Estructuras en forma de árbol), incluso puede alcanzar la complejidad O (1) amortizada tanto para leer como para actualizar el gráfico inmutable, lo cual es impresionante.
La idea es que cada objeto, además de almacenar su estado inmutable actual, reserve espacio para registrar cambios. Cuando desee crear un nuevo gráfico inmutable a partir de uno existente mediante la aplicación de cambios, debe considerar dos casos:
El objeto que desea cambiar todavía tiene espacio para cambios. Puede almacenar los cambios directamente en el objeto.
El objeto no tiene más espacio para cambios. Crea una nueva instancia basada en los valores actuales y los registros de cambio vacíos. Ahora necesita actualizar recursivamente todos los objetos que hacen referencia al objeto antiguo para hacer referencia al nuevo objeto.
Si el objeto de referencia en sí tiene espacio para cambios, puede almacenar el cambio en la referencia directamente, de lo contrario, se conecta en cascada de forma recursiva.
Si bien este esquema admite la creación eficiente de nuevas generaciones de gráficos de objetos inmutables, complica la lectura, ya que ahora debe especificar a qué "versión" desea acceder al leer datos de un objeto inmutable. Esto se debe a que el objeto inmutable puede tener información para varias versiones debido a los registros de cambios almacenados, por lo que debe especificar qué versión desea ver.
Al implementar esto, trato de ocultar esta complejidad en la API. Cuando hago referencia a algo en el gráfico inmutable desde el exterior, uso trozos generados en lugar de referencias directas, por lo que las personas que llaman no necesitan seguir pasando la versión deseada manualmente. Esto hace que las referencias sean un poco más caras que un puntero directo, pero creo que vale la pena.
fuente