Pelo que posso ver, existem duas formas difundidas de gerenciamento de recursos: destruição determinística e explícita. Exemplos do primeiro seriam destruidores de C ++ e ponteiros inteligentes ou o sub DESTROY de Perl, enquanto um exemplo do último seria o paradigma de blocos para gerenciar recursos do Ruby ou a interface IDispose do .NET.
Os idiomas mais recentes parecem optar pelo último, talvez como efeito colateral do uso de sistemas de coleta de lixo da variedade sem contagem de referência.
Minha pergunta é a seguinte: dado que os destruidores de ponteiros inteligentes ou sistemas de coleta de lixo com contagem de referência - quase a mesma coisa - permitem a destruição implícita e transparente de recursos, é uma abstração menos vazante do que os tipos não determinísticos que dependem de explícitos notação?
Vou dar um exemplo concreto. Se você tiver três subclasses C ++ de uma única superclasse, uma poderá ter uma implementação que não precise de nenhuma destruição específica. Talvez faça sua mágica de outra maneira. O fato de não precisar de nenhuma destruição especial é irrelevante - todas as subclasses ainda são usadas da mesma maneira.
Outro exemplo usa blocos Ruby. Duas subclasses precisam liberar recursos; portanto, a superclasse opta por uma interface que use um bloco no construtor, mesmo que outras subclasses específicas possam não precisar, pois não exigem destruição especial.
É o caso de o último vazar detalhes da implementação da destruição de recursos, enquanto o primeiro não?
EDIT: Comparando, digamos, Ruby para Perl pode ser mais justo, pois um tem destruição determinística e o outro não, mas ambos são coletados de lixo.
fonte
(*ptr).Message()
ou equivalenteptr->Message()
. Há um conjunto infinito de expressões permitidas, como((*ptr))->Message
também é equivalente. Mas todos eles se resumem aexpressionIdentifyingAnObject.Message()
Respostas:
Seu próprio exemplo responde à pergunta. A destruição transparente é claramente menos infiltrada do que a destruição explícita. Pode vazar, mas é menos gotejante.
Destruição explícita é análoga a malloc / free em C com todas as armadilhas. Talvez com um pouco de açúcar sintático para parecer baseado em escopo.
Alguns dos benefícios da destruição transparente sobre explícita: -
mesmo padrão de uso - você
não pode esquecer de liberar o recurso.
- os detalhes limpos não desarrumam a paisagem no ponto de uso.
fonte
A falha na abstração não é, na verdade, o fato de que a coleta de lixo não é determinística, mas a ideia de que os objetos estão "interessados" nas coisas às quais eles mantêm referências e não estão interessados nas coisas às quais não se apegam. referências. Para ver o porquê, considere o cenário de um objeto que mantém um contador de quantas vezes um controle específico é pintado. Na criação, ele assina o evento "paint" do controle e, ao descartá-lo, cancela a inscrição. O evento click simplesmente incrementa um campo e um método
getTotalClicks()
retorna o valor desse campo.Quando o objeto de contador é criado, ele deve fazer com que uma referência seja armazenada dentro do controle que está monitorando. O controle realmente não se importa com o objeto de contador e seria tão feliz se o objeto de contador e a referência a ele deixassem de existir, mas enquanto a referência existir, ele chamará o manipulador de eventos desse objeto toda vez pinta a si próprio. Essa ação é totalmente inútil para o controle, mas seria útil para qualquer um que invocasse
getTotalClicks()
o objeto.Se, por exemplo, um método fosse criar um novo objeto "contador de tinta", executar alguma ação no controle, observar quantas vezes o controle foi repintado e abandonar o objeto do contador de tinta, o objeto permaneceria inscrito no evento mesmo embora ninguém se importasse se o objeto e todas as referências a ele simplesmente desaparecessem. Os objetos não se qualificariam para coleta, no entanto, até que o controle em si seja. Se o método fosse invocado milhares de vezes durante a vida útil do controle [um cenário plausível], isso poderia causar um estouro de memória, mas pelo fato de que o custo de N invocações provavelmente seria O (N ^ 2) ou O (N ^ 3), a menos que o processamento da assinatura fosse muito eficiente e a maioria das operações não envolvesse pintura.
Esse cenário específico pode ser tratado dando ao controle manter uma referência fraca ao objeto de contador em vez de um forte. Um modelo de assinatura fraca é útil, mas não funciona no caso geral. Suponha que, em vez de querer ter um objeto que monitore um único tipo de evento a partir de um único controle, alguém queira ter um objeto de log de eventos que monitore vários controles, e o mecanismo de manipulação de eventos do sistema seja tal que cada controle precise de uma referência para um objeto de log de eventos diferente. Nesse caso, o objeto que vincula um controle ao log de eventos deve permanecer ativo apenas enquanto amboso controle que está sendo monitorado e o log de eventos permanecem úteis. Se nem o controle nem o log de eventos mantiverem uma forte referência ao evento vinculado, ele deixará de existir, mesmo sendo "útil". Se um deles mantiver um evento forte, a vida útil do objeto vinculado poderá ser estendida inutilmente, mesmo que o outro morra.
Se nenhuma referência a um objeto existe em qualquer parte do universo, o objeto pode ser considerado inútil e eliminado da existência. O fato de existir uma referência a um objeto, no entanto, não implica que o objeto seja "útil". Em muitos casos, a utilidade real dos objetos dependerá da existência de referências a outros objetos que - da perspectiva do GC - não têm nenhuma relação com eles.
Se os objetos forem notificados deterministicamente quando ninguém estiver interessado neles, eles poderão usar essas informações para garantir que qualquer pessoa que se beneficie desse conhecimento seja informada. Na ausência de tal notificação, no entanto, não há uma maneira geral de determinar quais objetos são considerados "úteis" se se conhecer apenas o conjunto de referências existentes, e não o significado semântico associado a essas referências. Portanto, qualquer modelo que suponha que a existência ou não de referências seja suficiente para o gerenciamento automatizado de recursos estaria condenado, mesmo que o GC pudesse detectar instantaneamente o abandono de objetos.
fonte
Nenhum "destruidor ou outra interface que diga" essa classe deve ser destruída "é um contrato dessa interface. Se você criar um subtipo que não exija destruição especial, eu estaria inclinado a considerar que uma violação do Princípio de Substituição de Liskov .
Quanto ao C ++ vs. outros, não há muita diferença. O C ++ força essa interface em todos os seus objetos. As abstrações não podem vazar quando solicitadas pelo idioma.
fonte
DerivedFooThatRequiresSpecialDestruction
só pode ser criado pelo código que chamanew DerivedFooThatRequiresSpecialDestruction()
. Por outro lado, um método de fábrica que retornasse umDerivedFooThatRequiresSpecialDestruction
código que não esperava algo que exigisse destruição seria uma violação do LSP.Ter que observar os ciclos manualmente não é implícito nem transparente. A única exceção é um sistema de contagem de referência com um idioma que proíbe ciclos por design. Erlang pode ser um exemplo desse sistema.
Então, ambas as abordagens vazam. A principal diferença é que os destruidores vazam por toda parte no C ++, mas
IDispose
são muito raros no .NET.fonte