基底クラスの関数を派生クラスで書き換える(横取りする、上書きする)ことをオーバーライド
と言う。つまり基底クラスのある関数と同じ名前、同じ引数、同じ戻り値で、中身の違う
関数を派生クラスで定義することを言う。仮想関数とは、オーバーライドをするための
メカニズムである。つまり基底クラスの動作または機能をあとから作成した派生クラスに
よってカスタマイズする手段を提供する。
メンバー関数の定義中に、戻り値の型の前に「virtual」というキーワードを付することにより、
その関数は仮想関数となる。以下に簡単な例を示す。
仮想関数 | 非仮想関数 |
// 親クラス
class Base{
public:
virtual void func(){
cout << "Base::func" << endl;
}
};
// 子クラス
class Deriv : public Base{
public:
virtual void func(){
cout << "Deriv::func" << endl;
}
};
void main(){
Base base;
Deriv deriv;
base.func();
deriv.func();
}
|
// 親クラス
class Base{
public:
void func(){
cout << "Base::func" << endl;
}
};
// 子クラス
class Deriv : public Base{
public:
void func(){
cout << "Deriv::func" << endl;
}
};
void main(){
Base base;
Deriv deriv;
base.func();
deriv.func();
}
|
Base::func
Deriv::func
|
Base::func
Deriv::func
|
上記の例は簡単な例で、インスタンスを通じて関数を呼び出した場合である。
インスタンスを通じて関数を呼び出す場合、「virtual」を付けても付けなくても
動作は変わらない。
仮想関数の効果が発揮されるのは、ポインタを通じて関数を呼び出す場合や、
参照を通じて関数を呼び出す場合である。
仮想関数 | 非仮想関数 |
// 親クラス
class Base{
public:
virtual void func(){
cout << "Base::func" << endl;
}
};
// 子クラス
class Deriv : public Base{
public:
virtual void func(){
cout << "Deriv::func" << endl;
}
};
void main(){
Base* pBase1 = new Base();
Base* pBase2 = new Deriv();
Deriv* pDeriv = new Deriv();
pBase1->func();
pDeriv->func();
pBase2->func();
delete pBase1;
delete pBase2;
delete pDeriv;
}
|
// 親クラス
class Base{
public:
void func(){
cout << "Base::func" << endl;
}
};
// 子クラス
class Deriv : public Base{
public:
void func(){
cout << "Deriv::func" << endl;
}
};
void main(){
Base* pBase1 = new Base();
Base* pBase2 = new Deriv();
Deriv* pDeriv = new Deriv();
pBase1->func();
pDeriv->func();
pBase2->func();
delete pBase1;
delete pBase2;
delete pDeriv;
}
|
Base::func
Deriv::func
Deriv::func
|
Base::func
Deriv::func
Base::func
|
基底クラスであるBaseポインタ型に、派生クラスであるDerivをnewした場合(pBase2)を
見てください。非仮想関数の場合は、Baseクラスのfunc関数が呼ばれています。これは
ポインタがBaseクラスのポインタだからです。(その中身がBaseクラスだろうが、Derivクラス
だろうが関係なくポインタの型であるBaseのfunc関数が呼ばれます。)
それに対して仮想関数では、ポインタが基底クラスであるBaseクラスののポインタである
にも関らず、その中身が派生クラスであるので、Derivクラスのfunc関数が呼び出されています。
非仮想関数では、実態が派生クラスだろうが基底クラスだろうが、その入れ物を示す
ポインタや参照の型を見て、コンパイラがどちらのクラスの関数を呼び出すかを決めます。
これを静的結合といいます。
これに対して仮想関数では、コンパイラがどちらのクラスの関数を呼び出すのか決める
のではなく、実行時にポインタや参照が、その実態は派生クラスなのか基底クラスなのかを調べ、
動的にどちらの関数を呼び出すかを決定します。これを動的結合といいます。なお、
動的結合のメカニズムは後述します。
// 親クラス
class Base{
public:
virtual void func(){
cout << "Base::func" << endl;
}
virtual void func2(){
cout << "Base::func2" << endl;
}
};
// 子クラス
class Deriv : public Base{
public:
virtual void func(){
cout << "Deriv::func" << endl;
}
virtual void func2(){
Base::func2(); // 親クラスのfunc2を呼び出す
cout << "Base::func2" << endl;
}
};
void main(){
Base* pBase = new Deriv();
base->func();
base->Base::func();
cout << endl;
base->func2();
delete pBase;
}
|
Deriv::func
Base::func
Base::func2
Deriv::func2
|
また、派生クラスの仮想関数から親クラスの関数を呼び出すことも可能である。さらに、
スコープ演算子「::」を利用することにより、明示的に基底クラスの関数を呼び出すことも
可能である。(この場合は静的結合になる。)
なお、基底クラスの関数が仮想関数の場合、その派生クラスでオーバーライドする
関数に「virtual」を付けなくても、自動的にその関数も仮想関数となる(上記の例では
Derivクラスのfunc関数)。しかし、一般的に省略せずに、派生クラスでも「virtual」を
付ける場合が多い。