C++では、親クラスで定義した関数を子クラスで「上書き」できる仕組みがあります。これをオーバーライドと呼びます。さらに、親クラス型のポインタや参照を通じて、実際には子クラスごとの動作を呼び分けられる性質をポリモーフィズム(多態性)といいます。
ゲーム開発では「敵キャラごとに異なる攻撃方法を持たせる」などの場面で活用されます。
混同しやすいですが、この2つは全く別の仕組みです。
| 用語 | 意味 | 説明 |
|---|---|---|
| オーバーロード | 同名関数の引数違い定義 | 同じクラス内で、同じ名前でも「引数の型や数」が異なる関数を複数定義すること |
| オーバーライド | 子クラスで関数上書き | 親クラスの仮想関数 virtual を子クラスで上書きすること |
class 親クラス {
public:
virtual void 関数名();
};
class 子クラス : public 親クラス {
public:
void 関数名() override; // 上書き
};
virtual を付けると、オーバーライド可能になるoverride を付けると、上書きしていることを明示できる// 19_enemy_override.cpp
#include <iostream>
using namespace std;
class Enemy {
public:
virtual void attack() {
cout << "敵が攻撃した!" << endl;
}
virtual ~Enemy() {} // 基底クラスは仮想デストラクタを用意(delete 時の安全のため)
};
class Slime : public Enemy {
public:
void attack() override {
cout << "スライムが体当たりしてきた!" << endl;
}
};
class Goblin : public Enemy {
public:
void attack() override {
cout << "ゴブリンが棍棒を振り下ろした!" << endl;
}
};
int main() {
Enemy* enemies[2];
enemies[0] = new Slime();
enemies[1] = new Goblin();
for (int i = 0; i < 2; i++) {
enemies[i]->attack(); // ポリモーフィズム:同じ呼び出しで違う動作
}
for (int i = 0; i < 2; i++) {
delete enemies[i];
}
return 0;
}
実行結果(例)
スライムが体当たりしてきた! ゴブリンが棍棒を振り下ろした!
Enemy* 型でまとめても、実際には子クラスごとの動作が呼ばれる。Character に speak() を定義し、デフォルトでは「……」と表示するHero と Villain(悪役)を作り、それぞれ違うセリフを speak() で表示するCharacter* の配列に Hero と Villain を入れ、for文で speak() を呼ぶ// 19_character_override.cpp
#include <iostream>
using namespace std;
class Character {
public:
virtual void speak() {
cout << "……" << endl;
}
};
class Hero : public Character {
public:
void speak() override {
cout << "勇者:世界を救うのは僕だ!" << endl;
}
};
class Villain : public Character {
public:
void speak() override {
cout << "魔王:この世界は我のものだ!" << endl;
}
};
int main() {
Character* chars[2];
chars[0] = new Hero();
chars[1] = new Villain();
for (int i = 0; i < 2; i++) {
chars[i]->speak();
delete chars[i];
}
return 0;
}
// 19_enemy_override.cpp
#include <iostream>
using namespace std;
class Enemy {
public:
virtual void attack() {
cout << "敵が攻撃した!" << endl;
}
virtual ~Enemy() {}
};
class Slime : public Enemy {
public:
void attack() override {
cout << "スライムが体当たりしてきた!" << endl;
}
};
class Goblin : public Enemy {
public:
void attack() override {
cout << "ゴブリンが棍棒を振り下ろした!" << endl;
}
};
int main() {
Enemy* enemies[2];
enemies[0] = new Slime();
enemies[1] = new Goblin();
for (int i = 0; i < 2; i++) {
enemies[i]->attack();
}
for (int i = 0; i < 2; i++) {
delete enemies[i];
}
return 0;
}
19_01_Override の 19_enemy_override.cpp をコピーして 19_02_OverrideMod に貼り付け、次の変更を加えてみましょう:
Dragon クラスを追加し、attack() で「ドラゴンが炎を吐いた!」と表示するenemies 配列を3つに増やし、Dragon も含めるEnemy の attack() 本体も何か表示するよう変更する次のコードを実行したとき、出力はどれか。
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() { cout << "..." << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "ワン!" << endl; }
};
int main() {
Animal* a = new Dog();
a->speak();
delete a;
}
Animal* 型のポインタでも、実際に指しているオブジェクトは Dog なので、virtual 関数により Dog::speak() が呼ばれる(ポリモーフィズム)。
子クラスで override キーワードを付ける目的として正しいものはどれか。
override を付けると、親クラスに同じシグネチャの virtual 関数が存在しない場合にコンパイルエラーになる。うっかりスペルミスなどでオーバーライドできていない状況を防げる。
次のコードで e->attack() の呼び出しが「ゴブリンが棍棒を振り下ろした!」と表示されるのはなぜか。
Enemy* e = new Goblin();
virtual 関数は「動的ディスパッチ」により、ポインタの型ではなく実際に指しているオブジェクトの型に基づいて呼び出す関数が決まる。これがポリモーフィズムの本質。
次のコードの出力として正しいものはどれか。
#include <iostream>
using namespace std;
class Base {
public:
void greet() { cout << "Base" << endl; }
};
class Child : public Base {
public:
void greet() { cout << "Child" << endl; }
};
int main() {
Base* p = new Child();
p->greet();
delete p;
}
Base::greet() に virtual が付いていないため、ポインタの型(Base*)で静的に決まり、Base::greet() が呼ばれる。ポリモーフィズムを使うには virtual が必要。