オブジェクト変数を使い終わった後、必ず変数にNothingをセットしているコードが多いが、実はあれは別に無くても良い。
Nothingを代入することでオブジェクトが破棄されると思っている方も多いと思うが、厳密には違う。オブジェクトの破棄のタイミングは、オブジェクトがどこからも参照されなくなった時だ。
例えば次のコード。
Sub test() Dim C As New Collection With C .Add "A" .Add "B" .Add "C" Set C = Nothing Debug.Print .Item(1) Debug.Print .Item(2) Debug.Print .Item(3) End With End Sub
Set C = Nothingの後にDebug.Printしているが、結果はA B Cの順に出力される。
Withブロックから抜けるまではWithがオブジェクトを参照しており、CにNothingを設定してもオブジェクトは生きている。
ひとつのオブジェクトが何か所から参照されているかの数を、参照カウントといい、この参照カウントが0になった時点でオブジェクトは破棄される。上記のプログラムでは、With Cとした時点でCollectionは変数CとWithの2か所から参照されており、参照カウントは2である。Set C = Nothingとしても参照カウントは1つ残っており、オブジェクトが破棄されるのはWithブロックを抜けたタイミングである。
また、参照カウントが残っていたとしても、プログラムが終了するタイミングでオブジェクトは破棄される。
クラスモジュールのTerminateイベントを使って実験してみよう。Terminateとは「終了させる」という動詞で、オブジェクトが破棄されるときに実行されるイベントである。
余談だが、映画「ターミネーター」の題意は「終了させる者」つまり、人の人生を終了させる殺人マシンのことである。
ターミネーター:新起動/ジェニシス ブルーレイ+DVDセット(2枚組) [Blu-ray]
- 出版社/メーカー: パラマウント ホーム エンタテインメント ジャパン
- 発売日: 2015/11/18
- メディア: Blu-ray
- この商品を含むブログ (15件) を見る
他にもネットワークの機器にターミネーターという装置があり、これは配線の終端に取り付ける抵抗器のことを指す。
ちなみにターミナルはターミネートの名詞版。日本でも終着駅のことをターミナルと言ったりする。Terminalとは「終点」「終わり」の意味である。一方、英語では病気の末期患者なんかもターミナルと言う。
余談、おしまい。
さて、映画ターミネーターにちなんで、クラス名も「Terminator」としておこう。
オブジェクトが破棄される際にはイミディエイトウインドウにModelプロパティと、Hasta la vista, baby.と表示させる。
Public Model As String Private Sub Class_Terminate() Debug.Print Model; " : Hasta la vista, baby." End Sub
元ネタが分からない人はターミネーター2を字幕で見ると良い。
- 出版社/メーカー: ジェネオン・ユニバーサル
- 発売日: 2013/12/20
- メディア: Blu-ray
- この商品を含むブログ (5件) を見る
さて、Terminatorクラスが作成できたら、メインモジュールに次のコードを書いて実行してみよう。
Sub The_Terminator() Dim T As New Terminator T.Model = "T800" Set T = Nothing Debug.Print "Fin." End Sub
TにNothingをセットした時点で参照カウントが0になるので、実行結果はT800が破棄されてからFin.と表示される。
T800 : Hasta la vista, baby. Fin.
次にSet T = Nothingを消して実行してみよう。
Sub The_Terminator() Dim T As New Terminator T.Model = "T800" Debug.Print "Fin." End Sub
この場合は、Fin.が出力されてからT800が破棄される。
プログラムの終了によってT800は強制的に抹消されるのだ。
Fin. T800 : Hasta la vista, baby.
このケースのように、大抵の場合はわざわざNothingをセットしなくたってオブジェクトは破棄される。
Nothingをセットしないとオブジェクトがメモリに残るというのは勘違いから来るデマであり、このようにクラスのTerminateイベントで実証することで何が本当なのかが分かる。
私も場合によりわざわざNothingをセットすることはある。それは、そこでオブジェクトが破棄されますよと「明示的に」記しておきたい場合である。それはプログラミングスタイルの問題であり、好みの問題なので、メモリの話とは関係ない。特に趣味で書いているようなコードはその日の気分で極力明示的に書いてみたり、面倒くさくて暗黙的になったり、色々である。
※褒められた習慣ではないので、良いプログラマになりたい場合は真似しないように。
さて、次にNothingの代わりに次々と新しいTerminatorをセットしていくとどうなるか。
Sub The_Terminator2() Dim T As New Terminator T.Model = "T800" Debug.Print "Create new terminator" Set T = New Terminator T.Model = "T850" Debug.Print "Create new terminator" Set T = New Terminator T.Model = "T888" Debug.Print "Clear T" Set T = Nothing Debug.Print "Fin." End Sub
結果は次のとおり。
Create new terminator T800 : Hasta la vista, baby. Create new terminator T850 : Hasta la vista, baby. Clear T T888 : Hasta la vista, baby. Fin.
Tに新型ターミネーターがセットされた時点で、旧型ターミネーターは参照カウントが0になり、抹消される。
また、以下のようにT800を別の変数に入れておくと参照カウントが残るので、Fin.の後に最後に抹消されるようになる。
Sub The_Terminator2() Dim T As New Terminator T.Model = "T800" Dim T_BackUp As Terminator Set T_BackUp = T Debug.Print "Create new terminator" Set T = New Terminator T.Model = "T850" Debug.Print "Create new terminator" Set T = New Terminator T.Model = "T888" Debug.Print "Clear T" Set T = Nothing Debug.Print "Fin." End Sub
次に、オブジェクト変数TをStaticで宣言してみよう。
Sub Terminator_Genisys() Static T As New Terminator T.Model = "T3000" End Sub
この場合、プログラムが終了してもオブジェクトがメモリに残って破棄されず、次に呼び出した際はそのオブジェクトが再利用される。
次のように書けば確実に破棄されるが、このことにあまり意味は無い。
Sub Terminator_Genisys() Static T As New Terminator T.Model = "T3000" Set T = Nothing End Sub
なぜなら、Staticというのはそもそもメモリに残す意図で書くものなので、最後に消すんだったら最初からDimで良いのだ。
なお、次のようなコードならNothingを書く意味はある。
Sub Terminator_Genisys2() Static T As New Terminator Static Count As Long Count = Count + 1 If Count < 3 Then T.Model = "T3000" Debug.Print Count; "回目:"; T.Model Else Debug.Print Count; "回目:"; T.Model Set T = Nothing Count = 0 End If End Sub
このコードは実行するたびにイミディエイトウインドウにN回目:T3000と表示され、3回目の実行と同時にT3000が抹消される。
1 回目:T3000 2 回目:T3000 3 回目:T3000 T3000 : Hasta la vista, baby.
ちなみに、VBAのオブジェクトはプログラムの終了時に破棄されるが、外部のオブジェクトを利用する場合はプログラムが終了してもメモリに残るケースがある。
例えば次のコード。
Sub IE起動() Dim IE As Object Set IE = CreateObject("InternetExplorer.Application") IE.Visible = True End Sub
これはVBAからIEを起動しているが、変数IEからの参照は消えてもアプリケーション自体は閉じない。
このようにNothingを追加しても結果は同じである。
Sub IE起動() Dim IE As Object Set IE = CreateObject("InternetExplorer.Application") IE.Visible = True Set IE = Nothing End Sub
外部オブジェクトの破棄に必要なのは、オブジェクトに対するクローズ操作で、IEの場合はQuitメソッドの実行がそれに該当する。
Sub IE起動() Dim IE As Object Set IE = CreateObject("InternetExplorer.Application") IE.Visible = True IE.Quit End Sub
ここでもNothingを書くことでメモリから破棄されるという話ではないことが分かる。
つまり、プログラムの最後にはNothing処理が行われるので、明示的に書いてもいいし、書かなくても動作に支障はないということだ。
以下の記事でオブジェクトが破棄されるタイミングについて更に詳しく説明したので紹介。thom.hateblo.jp