Estou praticando o uso de objetos imutáveis em C ++. Meu objetivo pessoal é representar um gráfico genérico de objetos (em heap) com uma sequência de gráficos imutáveis.
Construir o gráfico de várias versões em si não é tão difícil. O problema é desempenho. O controle de versão de força bruta precisa de cópia completa do gráfico, e isso não era aceitável.
Tentei compartilhar nós inalterados. Mas neste caso, eu tenho um novo problema; referências. A referência a outro objeto deve ser atualizada no gráfico inteiro. Isso precisa visitar todos os nós para cada vez que der uma nova versão gráfica. E isso modifica os nós com referências, portanto eles também devem ser derivados (copiando). O desempenho não será melhor do que a cópia em força bruta.
Tanto quanto posso imaginar, não existe uma maneira realmente eficiente de representar a mutação do gráfico de objetos com estados imutáveis. Então, eu estou pedindo uma idéia sobre isso.
É possível representar a mutação do gráfico de objetos eficientemente com o estado imutável?
fonte
Respostas:
O que você está procurando é chamado de Estrutura de Dados Persistentes . O recurso canônico para estruturas de dados persistentes são as estruturas de dados puramente funcionais do livro de Chris Okasaki . Estruturas de dados persistentes têm despertado interesse nos últimos tempos devido à sua popularização em Clojure e Scala.
No entanto, por algum motivo estranho, os gráficos persistentes parecem ser ignorados. Temos listas, dezenas de diferentes tipos de árvores, matrizes, filas prioritárias, mapas, mas nenhum gráfico.
Realmente encontrei apenas um artigo: Gráficos totalmente persistentes - Qual escolher?
fonte
Se você não considerar as conexões entre os objetos como parte do seu recurso de versão (e você pode - nesse caso, provavelmente o seguinte não ajuda muito), considere dividir seus objetos em uma parte que represente a parte da conectividade do objeto e uma parte que representa o estado imutável.
Em seguida, você pode fazer com que cada um dos subobjetos de conectividade contenha um vetor dos estados com versão. Dessa forma, você pode operar com um número de versão gráfica para acessar o estado imutável apropriado.
Para evitar a necessidade de percorrer o gráfico inteiro sempre que houver uma atualização para um nó específico, você poderá fazer isso para que, se um nó for acessado com um número de versão maior que o número da versão atual do nó, a versão atual seja usada . Se o nó for atualizado, você preenche todas as versões intermediárias com a versão anterior - permitindo fazer atualizações preguiçosas no gráfico de objetos.
Se a conectividade entre objetos fizer parte do seu estado de versão, o anterior não funcionará. Mas talvez você possa estendê-lo da seguinte maneira:
Para cada objeto no gráfico, crie um "objeto de manipulação". O objeto de manipulação contém a lista de estados imutáveis com versão. Em vez de armazenar referências a objetos em qualquer um dos objetos do gráfico, você deve armazenar uma referência ao objeto de manipulação. Em seguida, cada referência aos objetos seria desreferenciada através do identificador, usando o identificador e o número da versão da visualização do gráfico de objeto que estava sendo processado no momento. Isso retornaria o estado imutável correto para o objeto. Os estados imutáveis usam as alças para se referir a outros objetos no gráfico, para que você sempre obtenha a data consistente para a versão do gráfico que deseja processar.
A mesma lógica descrita acima se aplica à atualização das versões nas alças - o que permite atualizações localizadas e preguiçosas.
fonte
Existe uma solução publicada para esse problema com muito boa complexidade de tempo amortizado, mas é difícil encontrar quando você não sabe exatamente o que procurar. Você pode encontrar um bom resumo nestas notas de aula (consulte a seção 2.2.3) ou leia o artigo original Tornando as estruturas de dados persistentes . Se o seu gráfico de objetos tiver conectividade limitada (por exemplo, estruturas em forma de árvore), você poderá atingir a complexidade O (1) amortizada, tanto para a leitura quanto para a atualização do gráfico imutável, o que é impressionante.
A idéia é que cada objeto, além de armazenar seu estado imutável atual, reserve espaço para registrar as alterações. Quando você deseja criar um novo gráfico imutável a partir de um existente aplicando alterações, é necessário considerar dois casos:
O objeto que você deseja alterar ainda tem espaço para alterações. Você pode armazenar as alterações diretamente no objeto.
O objeto não tem mais espaço para alterações. Você cria uma nova instância com base nos valores atuais e nos registros de alterações vazios. Agora você precisa atualizar recursivamente todos os objetos que fazem referência ao objeto antigo para fazer referência ao novo objeto.
Se o próprio objeto de referência ainda tiver espaço para alterações, você poderá armazenar diretamente a alteração na referência, caso contrário, ela será conectada em cascata recursivamente.
Embora esse esquema ofereça suporte à criação eficiente de novas gerações de gráficos de objetos imutáveis, isso complica a leitura, porque agora você precisa especificar qual "versão" você deseja acessar ao ler dados de um objeto imutável. Isso ocorre porque o objeto imutável pode ter informações para várias versões devido aos registros de alterações armazenados, portanto, é necessário especificar qual versão você deseja examinar.
Ao implementar isso, tento ocultar essa complexidade na API. Ao referenciar algo no gráfico imutável de fora, eu uso stubs gerados em vez de referências diretas, para que os chamadores não precisem continuar passando a versão desejada manualmente. Isso torna as referências um pouco mais caras do que um ponteiro direto, mas acho que vale a pena a conveniência.
fonte