割当と構築、解体と解放
C++ では、割当*1 (allocation) と構築 (construction)、解体 (destruction) と解放 (deallocation) はそれぞれ全く異なる概念だ。コードで見ると、分かりやすい:
/* 割当 */ void* const rawMemory_( ::operator new(sizeof(T)) ); /* 構築 */ T* const object_( new(rawMemory_) T() ); /* 解体 */ object_->~T(); /* 解放 */ ::operator delete(rawMemory_);
C++ では、「オブジェクト」とはメモリ中の領域のことではなく、その中にあるビットパターンとその解釈規則 (型) の組だ。
例え話をするのも何だが、メモリは土地、オブジェクトは建物。土地を買ってくるのとその上に建物を建てるのは別のことだし、建物を壊しても土地の権利を失うわけではない。イメージだけで捉えるのは問題だが、swap
関数のイメージもこうすると掴みやすい (かもしれない)*2。
例え話を続けるのも何だが、Java や C# では、土地の概念が稀薄で建物は空中を漂っている。フワフワ浮いてるだけなので、つなぎ止めておかないとそのうちどこかに行ってしまう。
要するに、Java/C# と C++ とは、まあ、それだけ違う言語だということです。
仮想デストラクタの明示的な呼び出し
オブジェクトの解体の例で、こう書いたことに注意して欲しい:
T*
が指している先が T
型のオブジェクトである保証は当然無い((この例を出したところの記述では明らかに T
型のオブジェクトですが、一般論としてです。))ので、「 T::~T()
という関数」の呼び出しを要求してはならない。呼び出さなければならないのは、「 ~T
というスロットに入っている仮想関数」だ。
普通の仮想関数と違うのは、スロットの名前が一定していないこと。スロットの名前は、“そのときのポインタの静的な型に「 ~
」を付けたもの”だ。そして、~T
という名前であっても、このスロットに入っているのは T::~T()
かもしれないし D::~D()
かもしれない((もちろん、この D
は T
の公開派生クラスです。))。