割当と構築、解体と解放

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
例え話を続けるのも何だが、JavaC# では、土地の概念が稀薄で建物は空中を漂っている。フワフワ浮いてるだけなので、つなぎ止めておかないとそのうちどこかに行ってしまう。
要するに、Java/C#C++ とは、まあ、それだけ違う言語だということです。

*1:ワリアテ = メモリの割り当て。「構築」「解体」「解放」が漢字2文字なので、バランスを取ってこう書いてみました。

*2:建物を動かして入れ替える、というのは不自然で分かりにくいですね。犬小屋ぐらいなら動かせるかな? まあ、所詮は例え話です。

仮想デストラクタの明示的な呼び出し

オブジェクトの解体の例で、こう書いたことに注意して欲しい:

◎デストラクタの仮想呼び出し

object_->~T();

×デストラクタの非仮想呼び出し

object_->T::~T();

T* が指している先が T 型のオブジェクトである保証は当然無い((この例を出したところの記述では明らかに T 型のオブジェクトですが、一般論としてです。))ので、「 T::~T() という関数」の呼び出しを要求してはならない。呼び出さなければならないのは、「 ~T というスロットに入っている仮想関数」だ。
普通の仮想関数と違うのは、スロットの名前が一定していないこと。スロットの名前は、“そのときのポインタの静的な型に「 ~ 」を付けたもの”だ。そして、~T という名前であっても、このスロットに入っているのは T::~T() かもしれないし D::~D() かもしれない((もちろん、この DT の公開派生クラスです。))。