仮想継承
次のようなクラス階層を考える。
bat の eat() メソッドを呼び出した場合、どうなるだろうか? 上の宣言では bat.eat() の呼び出しは曖昧である。bat.WingedAnimal.Animal::eat() なのか bat.Mammal.Animal::eat() なのか判らない。問題は、いわゆる多重継承の意味論が現実をモデル化したものではない点にある。感覚的に言えば、Animal が Animal でしかないように、Bat は Mammal であると同時に WingedAnimal である。しかしBat(こうもり)のMammal(哺乳類)性におけるAnimal(動物)性は、WingedAnimal(翼のある動物)性におけるAnimal(動物)性と同じである。
このような状況を菱形継承と呼び、これを解決するのが仮想継承である。
クラス宣言を次のように修正する。
これで、Bat::WingedAnimal の Animal 部分は、Bat::Mammal で使われている Animal と同一になり、つまり1つの Bat インスタンスには1つの Animal だけが対応することになって、Bat::eat() 呼び出しが曖昧でなくなる。
これは、Mammal と WingedAnimal にvtableポインタを伴うような実装になる。つまり、Mammal の先頭と Animal 部分の先頭とのメモリオフセットは実行時まで不明となる。従って、Bat は (vtable*, Mammal, vtable*, WingedAnimal, Bat, Animal) となる。1つのオブジェクトに2つの vtable ポインタがあるため、そのぶんだけオブジェクトのサイズが大きくなるが、Animal は1つだけになるので曖昧さがなくなる。2つの vtable ポインタは Mammal と WingedAnimal それぞれの Animal の仮想継承に対応して存在する。Bat 型の全オブジェクトの vtable * の値は同じだが、Bat 型オブジェクトはそれぞれユニークな Animal オブジェクトを内包する。別のクラス、例えば Squirrel(リス)が Mammal を継承する場合、Squirrel における Mammal オブジェクトの vtable* は Bat の場合とは異なる。ただし、オブジェクトにおける Bat 部分と Squirrel 部分が同じサイズであれば、vtable* は同じになる。仮想関数テーブルは実際には同一ということはないが、基本的に距離(オフセット)を格納しているという点で同じである。