LoginSignup
9
12

【VBA】オブジェクト変数はショートカット

Last updated at Posted at 2024-03-16

はじめに

VBAのオブジェクト変数について、スタックやヒープなどの概念を用いての説明は厳密にいうと正確ではないかもしれませんが、イメージがしやすいので、敢えてこれらの言葉を使ってまとめてみました。

スタックとヒープ

一般的なプログラミングのメモリ管理において、データ格納先のメモリ領域にはスタックヒープがあります。

スタックは、組み込み型(IntegerDoubleBooleanなど)の変数を格納するために使用され、後入れ先出し(LIFO)で管理されます。関数やプロシージャの実行が完了すると、そのスコープにある変数は自動的にスタックから削除されます。スタックで使用される記憶領域のサイズはデータ型ごとに決まっており、メモリを管理する上では単純な仕組みとなります。

一方、ヒープは動的なメモリ割り当てに使用され、プログラムの実行中にサイズが変動する可能性のあるオブジェクトなどのデータを格納します。オブジェクトを作成する際には、Newキーワードを使用して(あるいはCreateObject関数を使用して)ヒープ上にオブジェクトを割り当て、そのオブジェクトへの参照を変数に格納します。

扱われるデータの性質によって、これらのメモリ領域の区別が必要なことは容易に理解できます。

オブジェクト変数はショートカット

上の説明で、オブジェクト変数に格納されているのはオブジェクトへの参照ということでした。つまり、オブジェクトが割り当てられたヒープメモリのアドレスということです。
関数やプロシージャの引数としてRangeWorksheetDictionaryなどを渡したとしても単にアドレスを受け渡ししているだけで実体のコピーが生成されるわけではありません。
これはファイルシステムで言うところのショートカットに似ています。例えて言うと、オブジェクト変数はファイルそのものではなく、ファイルを開くためのショートカットです。

ByValとByRef

関数やプロシージャへの引数の渡し方としてByValByRefがあります。

  • ByVal(値渡し)は、関数やプロシージャに引数が渡されるとき、その値のコピーが作成され、関数またはプロシージャ内での変更は呼び出し元の変数に影響を与えません

  • ByRef(参照渡し)は、引数として変数が渡されるとき、その変数への参照が渡され、関数またはプロシージャ内での変更が呼び出し元の変数に影響を与えます

では、オブジェクトをByValの引数に渡した場合、オブジェクトのコピーが作成されるのでしょうか?そうではありません。オブジェクトの実体ではなくショーカットのコピーが渡されるだけです。また、ByValが呼び出し元に影響を与えないと言っても、ショートカットが指している先は同じヒープアドレスにあるオブジェクトの実体です。関数またはプロシージャ内のオブジェクト変数でそのオブジェクトのプロパティを書き換えれば、呼び出し元のプロパティも当然書き換わります。ByValにより呼び出し元の変数とプロシージャ内の変数は別物になりましたが、中に持っているヒープアドレスは同じ実体を指しているからです。

Nothingの勘違い

では、この引数のオブジェクト変数にNothingを代入するとどうなるでしょうか。ここで勘違いするかもしれませんが、ヒープにあるオブジェクトの実体が即座に解放されるわけではありません。代入の瞬間に消去されるのはオブジェクト変数が保持しているショートカット(ヒープ上のアドレス)だけです。また、呼び出し元のオブジェクト変数は影響を受けず、ショートカット機能を保持したままとなります(ByValの場合です)。

解放はいつか?

次に、呼び出し元のオブジェクト変数にNothingを代入した場合を考えてみましょう。同様に、ショートカット(ヒープ上のアドレス)が消去されますが、もしこのオブジェクト変数が実行中のプロシージャ内で宣言されている場合、即座に解放されます。どこからも参照されなくなった時が解放される時となります。

以下はオブジェクトが解放されるタイミングを確認する実験です。クラスモジュールでClass1を作り、解放時に呼ばれるTerminateイベントプロシージャがいつ動作するかを確認します。

Class1.cls
Private cName As String

Public Property Let Name(ByRef vl As String)
    cName = vl
End Property

' クラス解放時に呼ばれる
Private Sub Class_Terminate()
    Debug.Print cName & "クラス解放"
End Sub

以下のHogeプロシージャを実行すると、イミディエイトウインドにプロシージャ終了時クラス解放時にテキストメッセージが出力されます。

Module1.bas
' グローバル変数
Public zClass As Class1

Public Sub Hoge()
    Dim xClass As Class1: Set xClass = New Class1
    xClass.Name = "x"
    
    Dim yClass As Class1: Set yClass = New Class1
    yClass.Name = "y"
   
    ' グローバル変数への代入    
    Set zClass = New Class1
    zClass.Name = "z"
    
    ' xだけNothingしてみる
    Set xClass = Nothing
    
    Debug.Print "プロシージャ終了"
End Sub

出力結果は以下のとおりです。

出力結果
xクラス解放
プロシージャ終了
yクラス解放
  • xClassは、Nothingを代入した直後にTerminateが呼ばれます
  • yClassは、Nothingを代入していませんが、Hogeプロシージャ終了後にTerminateが呼ばれます
  • zClassは、Terminateが呼ばれず、オブジェクトは解放されません

オブジェクト変数がグローバル変数の場合、解放にはNothingの代入が必須となります。また、Nothingしなくても、変数が宣言されたプロシージャのスコープから抜けると自動的に解放されることがわかりました。

例外として、一部のオブジェクトにはCloseメソッドなどがあり、プログラマの責任でCloseして解放しなければならない場合もあります。

おわりに

以上の説明はVBAに限らず、他の言語でも共通する部分があります。
また、冒頭でも言っているとおり、この説明はVBAの文脈では正確なものではなく、イメージしやすいように表現したものであることをもう一度付け加えておきます:laughing:

9
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
12