仮想継承

C++プログラミング言語における継承の一種で、多重継承によって生じる問題(菱形継承問題)を解決するもの

仮想継承(かそうけいしょう、: Virtual inheritance)とは、C++プログラミング言語における継承の一種で、多重継承によって生じる問題(菱形継承問題)を解決するもの。どの上位クラスのメンバーを使うか曖昧さが生じる部分で、明確化する。仮想継承は、部分の合成という意味での継承よりも、継承が上位クラスへの制限を表している場合に使われる。多重継承の基底クラスは、virtual というキーワードで仮想継承であることを指定される。

問題 編集

次のようなクラス階層を考える。

class Animal 
{
    public:
        virtual void eat();
};

class Mammal : public Animal 
{
    public:
        virtual Color getHairColor();
};

class WingedAnimal : public Animal 
{
    public:
        virtual void flap();
};

// Bat(こうもり)は翼のある哺乳類(winged mammal)である
class Bat : public Mammal, public WingedAnimal {};

Bat bat;

bateat() メソッドを呼び出した場合、どうなるだろうか? 上の宣言では bat.eat() の呼び出しは曖昧である。bat.WingedAnimal.Animal::eat() なのか bat.Mammal.Animal::eat() なのか判らない。問題は、いわゆる多重継承の意味論が現実をモデル化したものではない点にある。感覚的に言えば、AnimalAnimal でしかないように、BatMammal であると同時に WingedAnimal である。しかしBat(こうもり)のMammal(哺乳類)性におけるAnimal(動物)性は、WingedAnimal(翼のある動物)性におけるAnimal(動物)性と同じである。

このような状況を菱形継承と呼び、これを解決するのが仮想継承である。

クラス表現 編集

仮想継承を解説する前に、C++での継承におけるクラス表現を簡単に説明しておく。継承は、メモリ上では親クラスの後に子クラスを置いたリストで表される。上記の Bat の場合、(Animal, Mammal, Animal, WingedAnimal, Bat) となり、Animal が二度出現しているため、曖昧さが生じる。

解決策 編集

クラス宣言を次のように修正する。

// 2つのクラスは Animal を仮想継承する
class Mammal : public virtual Animal 
{
    public:
        virtual Color getHairColor();
};

class WingedAnimal : public virtual Animal 
{
    public:
        virtual void flap();
};

// こうもりは依然として翼のある哺乳類である
class Bat : public Mammal, public WingedAnimal {};

これで、Bat::WingedAnimalAnimal 部分は、Bat::Mammal で使われている Animal と同一になり、つまり1つの Bat インスタンスには1つの Animal だけが対応することになって、Bat::eat() 呼び出しが曖昧でなくなる。

これは、MammalWingedAnimalvtableポインタを伴うような実装になる。つまり、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* は同じになる。仮想関数テーブルは実際には同一ということはないが、基本的に距離(オフセット)を格納しているという点で同じである。

外部リンク 編集