Pergunta

Em F # wikilivro sob encapsular mutável Estado seção, há um seguinte trecho de código .

> let incr =
    let counter = ref 0
    fun () ->
        counter := !counter + 1
        !counter;;

val incr : (unit -> int)

> incr();;
val it : int = 1

> incr();;
val it : int = 2

> incr();;
val it : int = 3

Na primeira, ele parecia bastante fácil de engolir o fato de que, incrementos de valor counter mutáveis ??toda incr é invocado.

Mas depois de pensar sobre isso por algum tempo, o que eu não conseguia entender era quando counter é libertado da pilha e também como counter ainda se refere ao valor anterior antes de ser incrementado. Como é counter que vive dentro de escopo de função incr sobreviver através de várias chamadas de função?

Assim principais perguntas são:

  • Quando é que counter libertado da pilha?
  • Não é counter um vazamento de memória?
Foi útil?

Solução

A distinção entre 'escopo léxico' (onde um nome tem significado no texto de um programa) e 'vida' (duração de tempo de execução entre quando o objeto é criado e destruído) às vezes pode ser confuso, pois muitas vezes estes dois são altamente correlacionados . No entanto, a técnica demonstrada por este exemplo é comum em linguagens funcionais: você dá um detalhe de implementação de um pequeno escopo léxico (que esconde os detalhes de implementação de chamadores), mas estender sua vida útil, capturando-o em um fechamento (de modo que a sua vida se torna o tempo de vida do objecto encerra - neste caso a função 'incr'). Esta é uma maneira comum de fazer encapsulamento em programação funcional (em contraste com a técnica de encapsulamento usual de público / privado em aulas de programação orientada a objetos).

Agora, neste exemplo particular, parece que 'incr' é uma função de nível superior, o que significa que seu valor dura a vida do programa (ou sessão interativa se digitação em fsi.exe). Você poderia chamar isso de 'vazamento', mas isso depende de intenção. Se você tem alguma contra-id único que você precisa para a vida inteira de todo o seu programa, então você vai ter que armazenar esse contador varable em algum lugar que tem a duração de todo o programa. Assim, ou este é 'um vazamento' ou 'um por recurso de design', dependendo de como 'incr' será usado (você vai precisar usar essa função para todo o resto do programa?). Em qualquer caso, o ponto-chave aqui é que 'incr' detém recursos de memória, por isso, se você não vai precisar desses recursos para sempre, você deve providenciar para o encerramento referenciado por 'incr' para se tornar inacessível quando ele não é mais necessário. Comumente isso pode ser, tornando-o local para alguma outra função, por exemplo.

let MyComplicatedFuncThatNeedsALocalCounter args =
    let incr = 
        // as before
    // other code that uses incr
    // return some result that does not capture incr

Outras dicas

Neste caso, incr é uma função de nível superior (implementado como um campo estático, se não me engano.) Ele mantém um fecho que por sua vez tem uma referência a essa célula ref counter nomeado. Enquanto existir este encerramento, a célula ref é mantido na memória.

Agora ligação deste nível superior vai realmente nunca se lixo coletado, pois é um estático readonly campo. (Em termos C #). Se você, no entanto, têm fechamentos como que com um limitado vitalícia (ligada localmente ou em um objeto), a célula ref será liberado quando o fecho é lixo coletado.

Contador está livre da pilha quando incr não está acessível. Não é um vazamento de memória por causa da coleta de lixo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top