在Encapsulating Mutable State部分下的F#WikiBook上,有以下代码段。
> 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
起初,似乎很容易接受这样一个事实:每次counter
被调用时,可变incr
值递增。
但是在考虑了一段时间后,我无法理解的是当counter
从堆中释放时,以及counter
在递增之前如何仍然引用先前的值。生活在counter
函数范围内的incr
如何通过多个函数调用生存?
所以主要问题是:
counter
什么时候从堆中解放出来?counter
吗?答案 0 :(得分:4)
“词法范围”(名称在程序文本中有意义)和“生命周期”(创建和销毁对象之间的运行时间)之间的区别有时会令人困惑,因为这两者通常是高度相关的。但是,此示例演示的技术在函数式语言中很常见:您为实现细节提供了一个小的词法范围(隐藏了调用者的实现细节),但通过在闭包中捕获它来延长其生命周期(以便其生命周期成为生命周期)封闭对象 - 在本例中为'incr'函数)。这是在函数式编程中进行封装的常用方法(与面向对象编程中类中公共/私有的常用封装技术形成对比)。
现在,在这个特定的例子中,看起来'incr'是一个顶级函数,这意味着它的值会持续到程序的生命周期(如果输入fsi.exe,则会持续交互式会话)。你可以称之为'泄漏',但这取决于意图。如果你有一个独特的id计数器,你需要整个程序的整个生命周期,那么你将必须存储它持续整个程序的计数器varable 某处。所以要么这是'泄漏'还是'按设计特征',取决于将如何使用'incr'(你需要在程序的其余部分使用该功能吗?)。在任何情况下,这里的关键点是'incr'保存内存资源,因此如果您不再需要这些资源,则应安排'incr'引用的闭包在不再需要时无法访问。通常这可能是通过使其成为其他功能的本地,例如
let MyComplicatedFuncThatNeedsALocalCounter args =
let incr =
// as before
// other code that uses incr
// return some result that does not capture incr
答案 1 :(得分:3)
在这种情况下,incr
是一个顶级函数(如果我没有弄错,则作为静态字段实现。)它包含一个闭包,该闭包又引用了名为{{1的ref单元格}}。只要存在此闭包,counter
单元就会保留在内存中。
现在这个顶级绑定确实永远不会被垃圾收集,因为它是一个静态只读字段。 (用C#术语)。但是,如果您使用有限生命周期(在本地或在对象中绑定)的闭包,则在关闭垃圾回收时将释放ref
单元格。
答案 2 :(得分:2)
计数器从堆中释放。由于垃圾收集,这不是内存泄漏。