コンストラクタ (メソッド)
デストラクタ (メソッド)
にあるように、クラスのインスタンスを生成、破棄するための特別なメソッドです。あまり知られていませんがコンストラクタとデストラクタには
メソッド呼び出しの処理 (プログラムの制御)
にあるように、Pointer型とByte型の2つの隠しパラメータが存在します(デフォルトのregister呼出規約ではEAXレジスタとDLレジスタ)。第1パラメータ(Pointer型)はSelf(C++のthisに相当)です。一方で第2パラメータ(Byte型)は真偽値(0または非0)であり、クラスメソッドとして呼び出される場合、つまり
Foo := TFoo.Create;
では1(真)が設定され、インスタンスメソッドとして呼び出される場合、つまりconstructor TFoo.Create;
begin
inherited;
end;
のinherited(継承元コンストラクタの呼び出し)では0(偽)が設定されます。コンストラクタでは先頭にこの第2パラメータをチェックして真の場合はインスタンス領域の確保と初期化が行われるようなコードが生成されます(確保したインスタンス領域のアドレスはEAXに格納され、呼出元に返されます)。またデストラクタでは末尾に第2パラメータのチェックとインスタンス領域の解放が行われるようなコードが生成されます。さらにコンストラクタでは内部で例外が生成されたときに自動的にデストラクタが呼び出され、初期化後に変更されたフィールドに対してリソースの解放を行うことでコンストラクタで例外を送出したときのリソースリークを防ぐような仕組みになっています。実際のコードで確認してみましょう(例はシングルトンパターンその1のUnit2をDelphi 2007でデバッグビルドしたものです)。
コンストラクタ呼び出し
FSingleton := TSingleton.Create;
はUnit2.pas.30: FSingleton := TSingleton.Create;
00457C35 B201 mov dl,$01
00457C37 A1D47B4500 mov eax,[$00457bd4]
00457C3C E80B000000 call TSingleton.Create
00457C41 A3CC0C4600 mov [$00460ccc],eax
となり、DLレジスタに0x01が設定されてコンストラクタ(TSingleton.Create)を呼び出しています。コンストラクタ本体の
constructor TSingleton.Create;
begin
inherited;
{ Initialize }
FTestValue := 0;
end;
はUnit2.pas.38: begin
00457C4C 53 push ebx
00457C4D 56 push esi
00457C4E 84D2 test dl,dl
00457C50 7408 jz $00457c5a
00457C52 83C4F0 add esp,-$10
00457C55 E8B2C0FAFF call @ClassCreate
00457C5A 8BDA mov ebx,edx
00457C5C 8BF0 mov esi,eax
Unit2.pas.40: inherited;
00457C5E 33D2 xor edx,edx
00457C60 8BC6 mov eax,esi
00457C62 E869BDFAFF call TObject.Create
Unit2.pas.43: FTestValue := 0;
00457C67 33C0 xor eax,eax
00457C69 894604 mov [esi+$04],eax
Unit2.pas.45: end;
00457C6C 8BC6 mov eax,esi
00457C6E 84DB test bl,bl
00457C70 740F jz $00457c81
00457C72 E8EDC0FAFF call @AfterConstruction
00457C77 648F0500000000 pop dword ptr fs:[$00000000]
00457C7E 83C40C add esp,$0c
00457C81 8BC6 mov eax,esi
00457C83 5E pop esi
00457C84 5B pop ebx
00457C85 C3 ret
と先頭でDLレジスタが非0ならばClassCreateを呼び出し、BLレジスタにDLレジスタの値を保存してからDLレジスタを0に変更して以下の処理を行い、最後にBLレジスタ(=呼び出されたときのDLレジスタ)が非0であればAfterConstructionを呼び出すようになっています。
次にデストラクタを見てみます。といってもサンプルではTObject.Freeで呼び出しが隠蔽されてしまっていますので、ここはDestroyを直接呼び出すようにしてみます。
FSingleton.Destroy;
はUnit2.pas.63: FSingleton.Destroy;
00457CD2 B201 mov dl,$01
00457CD4 A1CC0C4600 mov eax,[$00460ccc]
00457CD9 8B08 mov ecx,[eax]
00457CDB FF51FC call dword ptr [ecx-$04]
となり、やはりDLレジスタに0x01を格納してからデストラクタを呼び出しています(呼出先が[ecx-$04]というのはVMT経由でデストラクタのアドレスを取得しているためです)。デストラクタ本体の
destructor TSingleton.Destroy;
begin
{ Finalize }
inherited;
end;
はUnit2.pas.48: begin
00457C88 53 push ebx
00457C89 56 push esi
00457C8A E825C1FAFF call @BeforeDestruction
00457C8F 8BDA mov ebx,edx
00457C91 8BF0 mov esi,eax
Unit2.pas.52: inherited;
00457C93 8BD3 mov edx,ebx
00457C95 80E2FC and dl,$fc
00457C98 8BC6 mov eax,esi
00457C9A E851BDFAFF call TObject.Destroy
Unit2.pas.54: end;
00457C9F 84DB test bl,bl
00457CA1 7E07 jle $00457caa
00457CA3 8BC6 mov eax,esi
00457CA5 E8B2C0FAFF call @ClassDestroy
00457CAA 5E pop esi
00457CAB 5B pop ebx
00457CAC C3 ret
となり、まずBeforeDestructionが呼び出され、BLレジスタにDLレジスタの値を保存してから0xFCとANDをとって(DLレジスタは0x01 AND 0xFC = 0x00となっている)継承元デストラクタを呼び出し、最後にBLレジスタ(呼び出されたときのDLレジスタ)が非0であればClassDestroyを呼び出すようになっています。またデストラクタ内でExitしても
Unit2.pas.48: begin
00457C88 53 push ebx
00457C89 56 push esi
00457C8A E825C1FAFF call @BeforeDestruction
00457C8F 8BDA mov ebx,edx
00457C91 8BF0 mov esi,eax
Unit2.pas.50: Exit;
00457C93 EB0C jmp $00457ca1
Unit2.pas.54: inherited;
00457C95 8BD3 mov edx,ebx
00457C97 80E2FC and dl,$fc
00457C9A 8BC6 mov eax,esi
00457C9C E84FBDFAFF call TObject.Destroy
Unit2.pas.56: end;
00457CA1 84DB test bl,bl
00457CA3 7E07 jle $00457cac
00457CA5 8BC6 mov eax,esi
00457CA7 E8B0C0FAFF call @ClassDestroy
00457CAC 5E pop esi
00457CAD 5B pop ebx
00457CAE C3 ret
とBLレジスタのチェックにジャンプするようになっており、DLが非0の呼び出しでは途中でExitしても必ずClassDestroyが呼び出されます。
このような事情から、コンストラクタの呼び出しによるクラスインスタンスの生成をキャンセルするためにはコンストラクタ内で例外を送出し、最外側コンストラクタ先頭で確保したインスタンス領域をデストラクタで解放させるしかありません(コンストラクタをクラスメソッドで呼び出すと必ず領域確保が行われるため)。同様にデストラクタの呼び出しによるインスタンスの破棄をキャンセルするには最外側のデストラクタの末尾までのどこかの時点で必ず例外を送出してインスタンス領域の解放を防ぐ必要があります。
元ねたはDelphiクイックリファレンス (amazon)/Ray Lischner著/光田秀、竹田知生訳/オライリー・ジャパン/ISBN4-87311-040-8/4,725円。
0 件のコメント:
コメントを投稿