はじめに
C++の継承では、子クラス(派生クラス)は親クラス(基底クラス)の機能を引き継ぎます。しかし、時には「親クラスの機能の一部を、子クラス独自の振る舞いに**上書き(変更)**したい」という場面があります。
このように、親クラスから継承したメンバ関数の処理内容を、子クラスで再定義することを「オーバーライド (Override)」と呼びます。オーバーライドは、オブジェクト指向の三本柱の一つである**多態性(ポリモーフィズム)**を実現するための、非常に重要な仕組みです。
この記事では、関数のオーバーライドの基本的な方法と、それを正しく機能させるための virtual
キーワードの重要性について解説します。
メンバ関数をオーバーライドするサンプルコード
このコードは、Character
(キャラクター)という親クラスと、それを継承したPlayer
(プレイヤー)という子クラスを定義します。親クラスの introduce
関数を、子クラスでオーバーライドして、表示メッセージを変更します。
完成コード
#include <iostream>
#include <string>
using namespace std;
// --- 親クラス(基底クラス) ---
class Character {
public:
string name = "名無しのキャラクター";
// 1. 親クラスのメンバ関数
void attack() {
cout << name << "の攻撃!" << endl;
}
// 2. virtualキーワードを付けて、オーバーライドされる可能性があることを示す
virtual void introduce() {
cout << "私は " << name << " です。" << endl;
}
};
// --- 子クラス(派生クラス) ---
class Player : public Character {
public:
// 3. 親クラスのintroduce関数をオーバーライド
void introduce() {
cout << ">>>> こんにちは! 私はプレイヤーの " << name << " です。 <<<<" << endl;
}
};
int main() {
Player myPlayer;
myPlayer.name = "勇者";
// 親クラスから継承した、オーバーライドしていない関数を呼び出し
myPlayer.attack();
// 子クラスでオーバーライドした関数を呼び出し
myPlayer.introduce();
return 0;
}
実行結果
勇者の攻撃!
>>>> こんにちは! 私はプレイヤーの 勇者 です。 <<<<
コードの解説
オーバーライドのルール
子クラスでメンバ関数をオーバーライドするには、親クラスで定義されているメンバ関数と、完全に同じシグネチャ(関数名、引数リスト、const
の有無など)で、再度メンバ関数を定義する必要があります。
もし引数の型や個数が異なると、それはオーバーライドではなく、単なるオーバーロードとして扱われてしまうため、注意が必要です。
virtual
キーワードの役割
virtual void introduce() { ... }
親クラスのメンバ関数の宣言の先頭に virtual
キーワードを付けておくと、その関数は「仮想関数」になります。
virtual
の意味: 「この関数は、将来、子クラスでオーバーライドされる可能性がありますよ」とコンパイラに伝えるための印です。- なぜ必要か: ポインタを通じてオブジェクトを扱う際に、多態性を正しく実現するために
virtual
は不可欠です。virtual
が付いていない関数をオーバーライドしても、ポインタ経由で呼び出した際に、意図せず親クラスの関数が呼ばれてしまうことがあります。
経験則: クラスを設計する際、「このクラスは親クラスとして使われる可能性がある」そして「このメンバ関数は、子クラスで動作を変える必要があるかもしれない」と感じたら、予防的に virtual
を付けておくのが良い習慣です。
まとめ
今回は、C++の継承における関数のオーバーライドと、virtual
キーワードの役割について解説しました。
- オーバーライドとは、親クラスから継承したメンバ関数を、子クラスで**再定義(上書き)**すること。
- オーバーライドするには、親クラスの関数と完全に同じシグ-ネチャにする必要がある。
- 親クラス側で、オーバーライドされる可能性のある関数には
virtual
キーワードを付けて「仮想関数」としておくことが強く推奨される。
オーバーライドは、継承の利点を最大限に活かし、柔軟で拡張性の高いプログラムを設計するための中心的なテクニックです。