概要#
普通の継承は基底クラスを先に構築し、その後派生クラスを構築することができると言えますが、仮想継承は基底クラスを派生クラスに埋め込み、一度だけ構築することで、仮想基底クラスが一度だけ構築されることを保証します。
基底クラスのコンストラクタに出力文 "constructor" が含まれている場合、仮想継承と普通の継承では異なる出力結果が得られます。
以下のサンプルコードを考えてみましょう:
#include <iostream>
class Base {
public:
Base() {
std::cout << "constructor" << std::endl;
}
};
class Derived : public virtual Base {
public:
Derived() : Base() {
}
};
int main() {
Derived d;
return 0;
}
上記のコードでは、Derived
クラスが仮想継承方式で Base
クラスを継承し、コンストラクタ内で基底クラスのコンストラクタを呼び出しています。このコードを実行すると、"constructor" が一度だけ出力されることがわかります。これは、仮想継承が仮想基底クラス(ここでは Base
クラス)が一度だけ構築されることを保証するためであり、多層継承の場合でも同様です。したがって、派生クラス Derived
のコンストラクタ内で基底クラス Base
のコンストラクタが一度だけ呼び出されます。
次に、普通の継承を使用した場合を考えてみましょう:
#include <iostream>
class Base {
public:
Base() {
std::cout << "constructor" << std::endl;
}
};
class Derived : public Base {
public:
Derived() : Base() {
}
};
int main() {
Derived d;
return 0;
}
この例では、Derived
クラスが普通の継承方式で Base
クラスを継承し、コンストラクタ内で基底クラスのコンストラクタを呼び出しています。このコードを実行すると、"constructor" が二度出力されます。一度は基底クラスのコンストラクタ呼び出しから、もう一度は派生クラスのコンストラクタ呼び出しからです。
したがって、仮想継承は仮想基底クラスが一度だけ構築されることを保証し、普通の継承は各継承レベルで基底クラスのコンストラクタを呼び出します。これは、コンストラクタ呼び出しに関する仮想継承と普通の継承の重要な違いです。
仮想継承の場合、"constructor" の出力は最終派生クラスのコンストラクタが基底クラスのコンストラクタを呼び出すことから来ます。
以下のサンプルコードを考えてみましょう:
#include <iostream>
class Base {
public:
Base() {
std::cout << "constructor" << std::endl;
}
};
class Intermediate : public virtual Base {
public:
Intermediate() : Base() {
}
};
class Derived : public Intermediate {
public:
Derived() : Intermediate() {
}
};
int main() {
Derived d;
return 0;
}
この例では、中間クラス Intermediate
が仮想継承方式で Base
クラスを継承しています。派生クラス Derived
は Intermediate
クラスを継承することで、間接的に Base
クラスを仮想継承しています。
このコードを実行すると、"constructor" が一度だけ出力されます。この出力は最終派生クラス Derived
のコンストラクタが Intermediate
クラスのコンストラクタを呼び出し、Intermediate
クラスのコンストラクタが基底クラス Base
のコンストラクタを呼び出すことから来ています。
したがって、仮想継承では、"constructor" の出力は最終派生クラスのコンストラクタが仮想基底クラスのコンストラクタを呼び出すことから来ます。これにより、仮想基底クラスが一度だけ構築されることが保証されます。
普通の継承では、派生クラスオブジェクトを構築する際に、まず基底クラス部分が構築され、その後に派生クラス部分が構築されます。これは、基底クラスのコンストラクタが派生クラスのコンストラクタの前に呼び出されることを意味します。したがって、普通の継承では、基底クラスを先に構築し、その後に派生クラスを構築します。
一方、仮想継承では、基底クラスのコンストラクタは一度だけ呼び出されます。継承関係の階層がどれだけ深くても同様です。仮想継承は追加のポインタやメカニズムを導入し、仮想基底クラスを派生クラスに埋め込むことで、仮想基底クラスが一度だけ構築されることを保証します。派生クラスのコンストラクタ呼び出しチェーンの中で、最終派生クラスが仮想基底クラスのコンストラクタを呼び出す責任を負い、各派生クラスが基底クラスのコンストラクタを呼び出すわけではありません。
したがって、普通の継承は基底クラスを先に構築し、その後に派生クラスを構築することができ、仮想継承は基底クラスを派生クラスに埋め込み、一度だけ構築することで、仮想基底クラスが一度だけ構築されることを保証します。
この違いは、多重継承における問題、例えばダイヤモンド継承問題を解決するために存在します。仮想継承の導入により、多重継承の状況下で基底クラス部分のメンバーをより柔軟に管理し、初期化することが可能になります。
普通の構築は、基底クラスを先に構築し、その後に派生クラスを構築することに相当しますか?仮想継承は、基底クラスを派生クラスに埋め込み、一度だけ構築することに相当しますか?
普通の継承では、派生クラスオブジェクトを構築する際に、まず基底クラス部分が構築され、その後に派生クラス部分が構築されます。これは、基底クラスのコンストラクタが派生クラスのコンストラクタの前に呼び出されることを意味します。したがって、普通の継承では、基底クラスを先に構築し、その後に派生クラスを構築します。
一方、仮想継承では、基底クラスのコンストラクタは一度だけ呼び出されます。継承関係の階層がどれだけ深くても同様です。仮想継承は追加のポインタやメカニズムを導入し、仮想基底クラスを派生クラスに埋め込むことで、仮想基底クラスが一度だけ構築されることを保証します。派生クラスのコンストラクタ呼び出しチェーンの中で、最終派生クラスが仮想基底クラスのコンストラクタを呼び出す責任を負い、各派生クラスが基底クラスのコンストラクタを呼び出すわけではありません。
まとめ#
したがって、普通の継承は基底クラスを先に構築し、その後に派生クラスを構築することができ、仮想継承は基底クラスを派生クラスに埋め込み、一度だけ構築することで、仮想基底クラスが一度だけ構築されることを保証します。
この違いは、多重継承における問題、例えばダイヤモンド継承問題を解決するために存在します。仮想継承の導入により、多重継承の状況下で基底クラス部分のメンバーをより柔軟に管理し、初期化することが可能になります。