t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA Set Obj = Nothing は必要か

オブジェクト変数を使い終わった後、必ず変数に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とは「終了させる」という動詞で、オブジェクトが破棄されるときに実行されるイベントである。

余談だが、映画「ターミネーター」の題意は「終了させる者」つまり、人の人生を終了させる殺人マシンのことである。

他にもネットワークの機器にターミネーターという装置があり、これは配線の終端に取り付ける抵抗器のことを指す。

ちなみにターミナルはターミネートの名詞版。日本でも終着駅のことをターミナルと言ったりする。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を字幕で見ると良い。

さて、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

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。