O gerenciamento não determinístico de recursos é uma abstração com vazamento?

9

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.

Louis Jackman
fonte
5
Fico tentado a dizer "sim", mas gosto de ouvir o que os outros têm a dizer sobre isso.
Bart van Ingen Schenau 31/03
Destruição transparente de recursos? Além do fato de você precisar usar ponteiros inteligentes em vez de ponteiros normais? Eu não acho que isso seja mais transparente do que ter apenas um mecanismo (referências) para acessar objetos (em C ++ você tem pelo menos quatro ou cinco).
Giorgio
@Giorgio: "Maneiras de acessar um objeto" é bastante vago. Você quer dizer ler ou escrever? Qualificação Const / Volátil? Ponteiros não são realmente "uma maneira de acessar um objeto"; praticamente qualquer expressão resulta em um objeto e desreferenciar um ponteiro simplesmente não é tão especial.
MSalters
11
@ Giorgio: Nesse sentido OOP, você não pode enviar uma mensagem para um ponteiro C ++. Você precisa desreferenciar o ponteiro para enviar a mensagem (*ptr).Message()ou equivalente ptr->Message(). Há um conjunto infinito de expressões permitidas, como ((*ptr))->Messagetambém é equivalente. Mas todos eles se resumem aexpressionIdentifyingAnObject.Message()
MSalters
11
Com a recontagem, você precisa ter cuidado para evitar círculos. Portanto, essa abstração também vaza, apenas de uma maneira diferente.
CodesInChaos 12/03

Respostas:

2

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.

mike30
fonte
2

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.

supercat
fonte
0

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.

Telastyn
fonte
4
"Se você criar um subtipo que não exija destruição especial" Isso não é violação do LSP, já que no-op é um caso especial válido de destruição. O problema é quando você adiciona o requisito de destruição a uma classe derivada.
CodesInChaos 12/03
Estou ficando confuso aqui. Se for necessário adicionar código de destruição especial a uma subclasse C ++, ele não altera seus padrões de uso, porque é automático. Isso significa que a superclasse e a subclasse ainda podem ser usadas de forma intercambiável. Mas com notação explícita para gerenciamento de recursos, uma subclasse que precisa de destruição explícita tornaria seu uso incompatível com uma superclasse, não seria? (Supondo que a superclasse não precisava de destruição explícita.)
Louis Jackman
@CodesInChaos - ah sim, suponho que seja verdade.
Telastyn 12/03
@ljackman: Uma classe que requer destruição especial impõe um ônus a quem chama seu construtor para garantir que ele seja realizado. Isso não cria uma violação de LSP, pois DerivedFooThatRequiresSpecialDestructionsó pode ser criado pelo código que chama new DerivedFooThatRequiresSpecialDestruction(). Por outro lado, um método de fábrica que retornasse um DerivedFooThatRequiresSpecialDestructioncódigo que não esperava algo que exigisse destruição seria uma violação do LSP.
Supercat
0

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?

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 IDisposesão muito raros no .NET.

Jon Harrop
fonte
11
Exceto que os ciclos são extremamente raros e praticamente nunca ocorrem, exceto em estruturas de dados explicitamente cíclicas. A principal diferença é que os destruidores em C ++ são tratados adequadamente em todos os lugares, mas o IDispose raramente lida com o problema no .NET.
DeadMG
"Exceto ciclos são extremamente raros". Nas línguas modernas? Eu desafiaria essa hipótese.
Jon Harrop