System4.0 の変数の寿命とスコープ
クラスのインスタンスを作成した際に、いつコンストラクタが呼び出され、いつデストラクタが呼び出されるか、調べてみましょう。
1.
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; void game_main(void) { ScopeTest st; 'test'A; } |
この結果は、
コンストラクタ デストラクタ |
となり、game_main()関数を抜ける際に、きちんとデストラクタが呼び出されていることが分かります。
2.
別の関数で宣言してみます。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; void test1(void){ ScopeTest st; system.Output("test1_called\n"); } void game_main(void) { test1(); ScopeTest st; 'test'A; } |
この結果は、
コンストラクタ test1_called デストラクタ コンストラクタ デストラクタ |
となります。タイミング的にも問題なく、信頼して使えます。
3.
関数の内部での宣言を参照オブジェクトにしてみます。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; void test1(void){ ref ScopeTest st; system.Output("test1_called\n"); } void game_main(void) { test1(); ScopeTest st; 'test'A; } |
この結果は、
test1_called コンストラクタ デストラクタ |
となります。参照オブジェクトの宣言では、インスタンスの作成は行われません。
4.
参照オブジェクトにインスタンスを生成し代入してみます。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; void test1(void){ ref ScopeTest st; st<-new ScopeTest; system.Output("test1_called\n"); } void game_main(void) { test1(); ScopeTest st; 'test'A; } |
この結果は、
コンストラクタ test1_called デストラクタ コンストラクタ デストラクタ |
となります。
なんということでしょう、参照オブジェクトでも関数を抜ける際に、デストラクタが呼び出されています。
これについて、SDKのヘルプに以下の説明があります。
> newで作成されたオブジェクトの寿命は、そのnewが書かれたブロック内です。どこからも参照されなくなっても、そのブロック内であれば存在し続けます。 (Sys42SDK_20041224\熟練者向けセット\マニュアル\Sys42\html\lang\exp_new.html) |
補足 |
5.
それならば、newが書かれたブロックから外に出してみたら、どうでしょう。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; ref ScopeTest test1(void){ ref ScopeTest st; st<-new ScopeTest; system.Output("test1_called\n"); return st; } void game_main(void) { ref ScopeTest st_t1; st_t1<-test1(); ScopeTest st; 'test'A; } |
この結果は、
コンストラクタ test1_called コンストラクタ デストラクタ デストラクタ |
となります。どこかで参照され続けている限り、インスタンスは保持し続けられるのです。そして、参照が外れると、自動的に解放されます。
つまり、参照オブジェクトが解放される条件は、「ブロックの外に出ている」 かつ 「参照されていない」 事です。
5'.
じゃあ、グローバルで定義されていても大丈夫ですよね。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; ref ScopeTest st; void test1(){ st<-new ScopeTest; system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; } |
この結果は、
コンストラクタ test1_called |
となります。……、駄目ですね。
5''.
あれ、おかしいなぁ…と思って、こんなコードを書いてみる。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; class A{ public: ref ScopeTest st; A(){system.Output("class_A コンストラクタ\n");} ~A(){system.Output("class_A デストラクタ\n");} }; A ax; void test1(){ ax.st<-new ScopeTest; system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; } |
この結果は、
class_A コンストラクタ コンストラクタ test1_called class_A デストラクタ |
となります。……、やっぱり駄目ですね。
どうやら、グローバル領域の参照オブジェクトは、そのまま放置ではデストラクタは呼ばれないようです。
(上の例から分かるとおり、参照オブジェクトでなければ、グローバル領域でもデストラクタは呼ばれています)
5'''.
はい、対策です。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; ref ScopeTest st; void test1(){ st<-new ScopeTest; system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; st<-NULL; } |
この結果は、
コンストラクタ test1_called デストラクタ |
となります。問題ないですね。
つまり、グローバルな参照オブジェクトは、いらなくなったら明示的にNULL参照にしなければならない…ということです。
6(1).
よーし、参照オブジェクトを配列に入れちゃうぞ。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; array@ref ScopeTest ast; void test1(void){ ref ScopeTest st; st <- new ScopeTest; ast.PushBack(st); system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; } |
この結果は、コンパイルエラーになります。
game_main.jaf(8) : グローバル配列オブジェクト宣言中に【 ; , 】以外の無効な区切り記号【 ast 】が現れました |
どうやら、参照オブジェクト型の配列は作成できないようです。
6(2).
じゃあ、クラス型の配列を作っちゃおう。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; array@ScopeTest ast; void test1(void){ ref ScopeTest st; st <- new ScopeTest; ast.PushBack(st); system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; } |
この結果も、コンパイルエラーになります。
game_main.jaf(12) : デストラクタを含んでいる構造体配列【 array@ScopeTest 】へはPushBackできません |
どうやら、デストラクタを含むクラスの参照オブジェクトを挿入できないようです。
6(3).
じゃあ、デストラクタを無くしちゃおう。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} }; array@ScopeTest ast; void test1(void){ ref ScopeTest st; st <- new ScopeTest; ast.PushBack(st); system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; } |
この結果は、通りますが、、
コンストラクタ コンストラクタ test1_called |
二回、コンストラクタが呼び出されています。(game_main()関数での宣言は削除してあります)
これは、astへのPushBackが、1個拡張して(このときに1つインスタンスが作成される)、そこに上書きされているためだと推測できます。
どーしても、デストラクタが使いたい場合は、
6(4).
VB.NETとかの方法をまねて、Dispose()を呼び出すようにしてあげましょう。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} void Dispose(){system.Output("デストラクタもどき\n");} }; array@ScopeTest ast; void test1(void){ ref ScopeTest st; st <- new ScopeTest; ast.PushBack(st); system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; int i; for(i=0;i<ast.Numof();i++){ ast[i].Dispose(); } } |
この結果は、
コンストラクタ コンストラクタ test1_called デストラクタもどき |
となります。一個しか呼ばれていないのは、PushBackが勝手に作成した方は、勝手に解放しているためです。(きっと)
と、ここまでやって、賢明なる読者ならば、もう気づいたと思います。
それは、PushBackが勝手に一個作成する、と言うことです。
PushBackの内部では、おそらく array.Realloc(ast.Numof()+1); とやって、作成した場所に値を格納しているのでしょう。
なら、これを再現してやれば、配列にインスタンスを格納できるハズです。
6(5).
newは消えました。
class ScopeTest{ public: ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ\n");} }; array@ScopeTest ast; void test1(void){ ast.Realloc(ast.Numof()+1); system.Output("test1_called\n"); } void game_main(void) { test1(); 'test'A; } |
この結果は、
コンストラクタ test1_called デストラクタ |
となります。デストラクタも問題なく使えています。
やってほしいことそのままですよね。
7.
発展させてみました。
作成したインスタンスの参照オブジェクトを取得し、それを用いて文字列を設定しています。
class ScopeTest{ public: string mozi; ScopeTest(){system.Output("コンストラクタ\n");} ~ScopeTest(){system.Output("デストラクタ[%s]\n" % mozi);} }; void test2(ref array@ScopeTest ast,string mozi){ ast.Realloc(ast.Numof()+1); ref ScopeTest st; st <- ast[ast.Numof()-1]; st.mozi=mozi; system.Output("test2_called[%s]\n" % mozi); } void test1(void){ array@ScopeTest ast; test2(ast,"いっこめ"); test2(ast,"にこめ"); test2(ast,"さんこめ"); system.Output("test1_called\n"); } void game_main(void) { system.Output("--これから作成していきます--\n"); test1(); system.Output("--終わりました--\n"); 'test'A; } |
この結果は、
--これから作成していきます-- コンストラクタ test2_called[いっこめ] コンストラクタ test2_called[にこめ] コンストラクタ test2_called[さんこめ] test1_called デストラクタ[さんこめ] デストラクタ[にこめ] デストラクタ[いっこめ] --終わりました-- |
となります。
test1()関数で配列を作成しているので、test1()関数を抜けると、全てにデストラクタが発行されています。
完璧です。すばらしいです。パーフェクトです。
結論
System4.0の寿命、コンストラクタ、デストラクタ、スコープ、GCは、結構いい感じです。
クラスの配列で、デストラクタを使いたいなら、6(5)の方法を用います。
デストラクタが無いクラス、構造体のようなものならば、分かりやすい6(3)の方法でも問題ないと思います。
(多少、コストがかかると思いますが…)
※ 注意点というか、ちょっと残念な事は、コンストラクタに引数を指定できないという事です…。