2009-05-03 9 views
0

F#WikiBookのEncapsulating Mutable Stateセクションには、次のコードスニペットがあります。まず可変状態値はいつヒープから解放されますか?

> 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はメモリリークですか?

答えて

4

'レキシカルスコープ'(名前がプログラムのテキストに意味を持つ)と '生涯'(オブジェクトが作成され破壊されるまでの実行時間)の区別は、しばしば混乱することがあります。相関する。しかし、この例で示すテクニックは関数型言語では一般的です。実装の詳細に呼び出し側の実装の詳細を隠す小さなレキシカルスコープを与えますが、それをクロージャーでキャプチャして寿命を延ばす(寿命が生涯になるようにこの例では、 'incr'関数)を使用します。これは関数型プログラミングでカプセル化を行う一般的な方法です(オブジェクト指向プログラミングのクラスではpublic/privateの通常のカプセル化技法とは対照的です)。

ここで、 'incr'は最上位レベルの関数です。これは、プログラムの存続期間(またはfsi.exeに入力すると対話型セッション)で値が保持されることを意味します。これを「リーク」と呼ぶこともできますが、それは意図に依存します。プログラム全体のライフタイム全体にわたって必要な独自のIDカウンタがある場合は、そのカウンタを変数のどこかに格納する必要があります。はプログラム全体に適用されます。だから、これは "incr"がどのように使われるかによって(プログラムの残りの部分でその関数を使う必要があるのだろうか?)、 "漏れ"か "設計上の特徴によって"どちらかです。いずれにしても、ここで重要なのは、 'incr'がメモリリソースを保持していることです。そのリソースが永久に必要ない場合は、 'incr'で参照されるクロージャが不要になったときに到達できないようにする必要があります。一般に、これは他の機能にローカルにすることで可能です。この場合

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

カウンタは、incrにアクセスできないときにヒープから解放されます。ガベージコレクションのためにメモリリークではありません。

+0

はい、でもそれは役に立ちません。 GC環境であっても、メモリリークが発生する可能性があります。つまり、もはや使用されなくなったオブジェクトを誤って保持してしまったときです。 (インスタンスメソッドへの代理人は、例えば 'this'オブジェクトを保持します)。 –

+0

@Dave私はFSIからサンプルを実行していました。それは、FSIを終了するまで、一度は範囲から外れないことを意味しますか? – Sung

+0

はい、FSIでは、これはFSIセッションの生涯にわたってハングします。あなたが 'これを離れる'ことができるようにしたいなら、 'incr'を変更可能にしてください。一般に、FSIセッションは、トップレベルの名前が(シャドウイングのために到達不能になっても)バインドされないため、多くのリークが発生します。 – Brian

3

incrはトップレベルの関数である(私は間違っていない場合は、静的フィールドとして実装されています。)これは、順番にcounterという名前のその参照セルへの参照を持って閉鎖を保持しています。このクロージャが存在する限り、refセルはメモリ内に保持されます。

このトップレベルのバインディングは、静的な読み取り専用フィールドであるため、実際にガベージコレクションされることはありません。 (C#用語で)。しかし、(ローカルまたはオブジェクトにバインドされた)の有効期間が制限されているようなクロージャがある場合、refセルはクロージャがガベージコレクションされると解放されます。

関連する問題