19:オーバーライドとポリモーフィズム

はじめに

C++では、親クラスで定義した関数を子クラスで「上書き」できる仕組みがあります。これをオーバーライドと呼びます。さらに、親クラス型のポインタや参照を通じて、実際には子クラスごとの動作を呼び分けられる性質をポリモーフィズム(多態性)といいます。

ゲーム開発では「敵キャラごとに異なる攻撃方法を持たせる」などの場面で活用されます。

オーバーロードとオーバーライドの違い

混同しやすいですが、この2つは全く別の仕組みです。

用語意味説明
オーバーロード同名関数の引数違い定義同じクラス内で、同じ名前でも「引数の型や数」が異なる関数を複数定義すること
オーバーライド子クラスで関数上書き親クラスの仮想関数 virtual を子クラスで上書きすること

基本構文

class 親クラス {
public:
    virtual void 関数名();
};

class 子クラス : public 親クラス {
public:
    void 関数名() 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* 型でまとめても、実際には子クラスごとの動作が呼ばれる。

練習問題

  1. 親クラス Characterspeak() を定義し、デフォルトでは「……」と表示する
  2. 子クラス HeroVillain(悪役)を作り、それぞれ違うセリフを speak() で表示する
  3. Character* の配列に HeroVillain を入れ、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;
}

コーディング演習

演習1:基本コードを動かす

19_01_override.cpp を作成し、次のコードを入力して実行しましょう。
// 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;
}

演習2:コードを改造する

19_01_Override19_enemy_override.cpp をコピーして 19_02_OverrideMod に貼り付け、次の変更を加えてみましょう:

まとめ

理解度チェック

問題19-1

次のコードを実行したとき、出力はどれか。

#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() が呼ばれる(ポリモーフィズム)。

問題19-2

子クラスで override キーワードを付ける目的として正しいものはどれか。

解説を表示 正解:イ
override を付けると、親クラスに同じシグネチャの virtual 関数が存在しない場合にコンパイルエラーになる。うっかりスペルミスなどでオーバーライドできていない状況を防げる。

問題19-3

次のコードで e->attack() の呼び出しが「ゴブリンが棍棒を振り下ろした!」と表示されるのはなぜか。

Enemy* e = new Goblin();
解説を表示 正解:ウ
virtual 関数は「動的ディスパッチ」により、ポインタの型ではなく実際に指しているオブジェクトの型に基づいて呼び出す関数が決まる。これがポリモーフィズムの本質。

問題19-4

次のコードの出力として正しいものはどれか。

#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 が必要。