Propriedade compartilhada raramente faz sentido
Essa resposta pode ser um pouco fora da tangente, mas tenho que perguntar: quantos casos faz sentido do ponto de vista do usuário final compartilhar a propriedade ? Pelo menos nos domínios em que trabalhei, praticamente não havia, porque, caso contrário, isso implicaria que o usuário não precisaria simplesmente remover algo de uma só vez de um lugar, mas explicitamente removê-lo de todos os proprietários relevantes antes que o recurso fosse realmente removido do sistema.
Geralmente, é uma ideia de engenharia de nível inferior impedir que os recursos sejam destruídos enquanto outra coisa ainda está acessando, como outro encadeamento. Freqüentemente, quando um usuário solicita fechar / remover / excluir algo do software, ele deve ser removido o mais rápido possível (sempre que for seguro removê-lo), e certamente não deve demorar e causar um vazamento de recursos pelo tempo que for necessário. o aplicativo está sendo executado.
Como exemplo, um ativo de jogo em um videogame pode fazer referência a um material da biblioteca de materiais. Certamente, não queremos, digamos, um travamento de ponteiro oscilante se o material for removido da biblioteca de materiais em um segmento enquanto outro segmento ainda estiver acessando o material referenciado pelo ativo do jogo. Mas isso não significa que faz sentido que os recursos do jogo compartilhem a propriedade dos materiais que eles fazem referência com a biblioteca de materiais. Não queremos forçar o usuário a remover explicitamente o material da biblioteca de materiais e ativos. Queremos apenas garantir que os materiais não sejam removidos da biblioteca de materiais, o único proprietário sensato dos materiais, até que outras linhas tenham terminado de acessar o material.
Vazamentos de recursos
No entanto, trabalhei com uma equipe anterior que adotou a GC para todos os componentes do software. E enquanto isso realmente ajudou a garantir que nunca tivéssemos recursos destruídos enquanto outros threads ainda os estavam acessando, acabamos recebendo nossa parcela de vazamentos de recursos .
E esses não foram vazamentos triviais de recursos que perturbam apenas os desenvolvedores, como um kilobyte de memória vazada após uma sessão de uma hora. Foram vazamentos épicos, geralmente gigabytes de memória durante uma sessão ativa, levando a relatórios de erros. Como agora, quando a propriedade de um recurso está sendo referenciada (e, portanto, compartilhada na propriedade) entre, digamos, 8 partes diferentes do sistema, é preciso apenas uma falha para remover o recurso em resposta ao usuário solicitando sua remoção. vazar e possivelmente indefinidamente.
Portanto, nunca fui um grande fã do GC ou da contagem de referência aplicada em larga escala devido à facilidade com que eles criaram software com vazamento. O que anteriormente teria sido um travamento de ponteiro oscilante, fácil de detectar, se transforma em um vazamento de recursos muito difícil de detectar, que pode voar facilmente sob o radar dos testes.
Referências fracas podem atenuar esse problema se a linguagem / biblioteca as fornecer, mas achei difícil contratar uma equipe de desenvolvedores de conjuntos de habilidades mistas para poder usar consistentemente referências fracas sempre que apropriado. E essa dificuldade não estava relacionada apenas à equipe interna, mas a todos os desenvolvedores de plug-ins do nosso software. Eles também poderiam facilmente fazer com que o sistema vazasse recursos, apenas armazenando uma referência persistente a um objeto de maneiras que dificultavam o rastreamento do plug-in como o culpado; portanto, também obtivemos a maior parte dos relatórios de erros resultantes dos recursos do software. sendo vazado simplesmente porque um plug-in cujo código fonte estava fora do nosso controle falhou ao liberar referências a esses recursos caros.
Solução: Remoção Adiada Periódica
Então, minha solução, mais tarde, na qual apliquei meus projetos pessoais que me deram o melhor que encontrei nos dois mundos, foi eliminar o conceito de que, referencing=ownership
ainda assim, adiamos a destruição de recursos.
Como resultado, agora, sempre que o usuário faz algo que faz com que um recurso precise ser removido, a API é expressa em termos de apenas remover o recurso:
ecs->remove(component);
... que modela a lógica do usuário final de maneira muito direta. No entanto, o recurso (componente) pode não ser removido imediatamente se houver outros encadeamentos do sistema em sua fase de processamento em que eles possam acessar o mesmo componente simultaneamente.
Portanto, esses encadeamentos de processamento produzem tempo aqui e ali, o que permite que um encadeamento semelhante a um coletor de lixo seja ativado e " pare o mundo " e destrua todos os recursos que foram solicitados a serem removidos enquanto impedia que os encadeamentos processassem esses componentes até sua conclusão. . Eu ajustei isso para que a quantidade de trabalho que precise ser feita aqui seja geralmente mínima e não corte visivelmente as taxas de quadros.
Agora não posso dizer que este é um método testado e bem documentado, mas é algo que venho usando há alguns anos, sem dores de cabeça e sem vazamentos de recursos. Eu recomendo explorar abordagens como essa quando for possível para sua arquitetura se encaixar nesse tipo de modelo de simultaneidade, pois é muito menos trabalhoso do que GC ou ref-counting e não corre o risco desses tipos de vazamentos de recursos passarem pelo radar dos testes.
O único lugar em que achei útil a ref-counting ou GC é para estruturas de dados persistentes. Nesse caso, é o território da estrutura de dados, muito distanciado das preocupações dos usuários, e ali faz sentido que cada cópia imutável esteja compartilhando a propriedade dos mesmos dados não modificados.