【C++】ポリモーフィズムとは?|virtualとoverrideの使い方を初心者向けに解説

サムネイル画像 C++

今回は、C++での「ポリモーフィズム」について解説していきます。

「ポリモーフィズムって名前が難しくてよくわからない…」
「継承やvirtualと何が関係あるの?」
「クラスを学んだあとに出てくるけど、結局何が便利なの?」

こんな疑問はありませんか?

この記事を読み終えると、あなたはポリモーフィズムの意味・使いどころ・virtualとoverrideの基本をしっかり理解できるようになると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

ポリモーフィズムとは?

ポリモーフィズムとは、同じ呼び出し方でも、オブジェクトの種類によって動作が変わる仕組みのことです。

たとえば、敵クラスの Attack() という関数があったとして、スライムなら体当たり、ドラゴンなら炎を吐く、というように同じ関数名なのに中身が変わるイメージです。

C++では、主に継承virtual関数を使ってこの仕組みを実現します。

クラス自体がまだ不安な方は、先にクラスとは?を解説した記事を読んでおくと理解しやすいです。

なぜポリモーフィズムが必要?

ポリモーフィズムが便利なのは、処理の共通化がしやすくなるからです。

  • 敵ごとに違う攻撃処理を書きたい
  • でも呼び出す側は同じ書き方にしたい
  • if文だらけの分岐を減らしたい

こういう場面で、ポリモーフィズムを使うとコードがかなりスッキリします。

継承の基本や protected の使い方がまだ曖昧な方は、継承とprotectedを解説した記事もあわせて読むのがおすすめです。

使い方の基本

まずは一番シンプルな例を見てみましょう。

▼Character.h

#pragma once
#include <iostream>

class Character {
public:
    virtual void Attack() {
        std::cout << "キャラクターが攻撃した\n";
    }
};

▼Slime.h

#pragma once
#include "Character.h"

class Slime : public Character {
public:
    void Attack() override {
        std::cout << "スライムの体当たり!\n";
    }
};

▼Dragon.h

#pragma once
#include "Character.h"

class Dragon : public Character {
public:
    void Attack() override {
        std::cout << "ドラゴンの炎攻撃!\n";
    }
};

▼main.cpp

#include "Slime.h"
#include "Dragon.h"

int main() {
    Character* c1 = new Slime();
    Character* c2 = new Dragon();

    c1->Attack();
    c2->Attack();

    delete c1;
    delete c2;
    return 0;
}

実行結果

スライムの体当たり!
ドラゴンの炎攻撃!

呼び出しているのはどちらも Attack() ですが、実際に実行される内容はオブジェクトごとに変わっています。これがポリモーフィズムです。

virtualとoverrideの意味

ポリモーフィズムで重要なのが、virtualoverride です。

  • virtual:基底クラス側で「この関数は派生クラスで上書きできますよ」と示す
  • override:派生クラス側で「基底クラスの関数を上書きしています」と明示する

override を付けておくと、関数名や引数がズレていた時に気づきやすくなるので、初心者のうちから付けるクセをつけておくのがおすすめです。

【重要】私が実際にポリモーフィズムで困った体験談

私も自主制作のゲームで、敵ごとに攻撃処理を切り替えたいと思ってポリモーフィズムを使おうとしたことがあります。

最初は、敵の種類ごとに if 文で分岐して処理を書いていたのですが、敵が増えるたびに分岐がどんどん長くなってしまい、かなり見づらくなりました。さらに、攻撃演出やSEの再生処理も混ざってきて、修正するたびに別の敵の挙動まで壊してしまったことがあります。

そこで、基底クラスに Attack() を用意して、敵ごとに派生クラスで上書きする形に変えたところ、「呼び出し方は同じなのに、中身だけ敵ごとに変えられる」ので一気に管理しやすくなりました。

ただ、その時に一度、基底クラス側の関数に virtual を付け忘れてしまい、「あれ、スライム用の攻撃を書いたのに基底クラスの処理しか呼ばれない…」とハマりました。ポリモーフィズムは便利ですが、virtualを付けることと、ポインタや参照で扱うことがかなり大事だと実感しました。

ポリモーフィズム使用時のよくあるエラーと対処法

  • 基底クラスにvirtualを付け忘れる
    派生クラスで同名関数を書いても、期待通りに動的ディスパッチされません。基底クラスの関数にvirtualを付けましょう。
  • overrideを書かずに引数を間違える
    たとえば void Attack(int power) のように形が変わると、上書きではなく別関数扱いになります。overrideを付けるとミスに気づきやすいです。
  • 基底クラス型の変数にそのまま代入してしまう
    ポリモーフィズムを活かしたいなら、基底クラスのポインタや参照で扱うのが基本です。

実践例

次は、複数の敵を同じ書き方で処理する例です。

▼Enemy.h

#pragma once
#include <iostream>

class Enemy {
public:
    virtual void Update() {
        std::cout << "敵が行動する\n";
    }
};

▼Bat.h

#pragma once
#include "Enemy.h"

class Bat : public Enemy {
public:
    void Update() override {
        std::cout << "コウモリが空を飛び回る\n";
    }
};

▼Goblin.h

#pragma once
#include "Enemy.h"

class Goblin : public Enemy {
public:
    void Update() override {
        std::cout << "ゴブリンが剣で攻撃する\n";
    }
};

▼main.cpp

#include "Bat.h"
#include "Goblin.h"

int main() {
    Enemy* enemies[2];
    enemies[0] = new Bat();
    enemies[1] = new Goblin();

    for (int i = 0; i < 2; i++) {
        enemies[i]->Update();
    }

    for (int i = 0; i < 2; i++) {
        delete enemies[i];
    }

    return 0;
}

実行結果

コウモリが空を飛び回る
ゴブリンが剣で攻撃する

このように、配列やリストにまとめて扱う時にポリモーフィズムはかなり便利です。敵が増えても、呼び出し側のコードはほとんど変えずに済みます。

注意点

  • ポリモーフィズムは便利ですが、まずはクラス継承の基本理解が前提です
  • virtual を忘れると、思った通りに派生クラスの関数が呼ばれません
  • override を付けるとミス防止になります
  • 小規模な処理では無理に使わず、if文の方がわかりやすい場合もあります

まとめ

  • ポリモーフィズムとは、同じ呼び出し方で中身の動作を変えられる仕組み
  • C++では主に継承virtual関数で実現する
  • 派生クラス側では override を付けると安全
  • 敵・武器・UI部品など種類が増える処理で特に効果を発揮する
  • まずは「基底クラスのポインタから派生クラスの関数が呼ばれる」という感覚をつかむのが大切

ポリモーフィズムは最初は少し難しく感じますが、ゲーム制作やアプリ開発でクラス設計を進めるほど便利さを実感しやすい機能です。

ここまで読んでくださり、ありがとうございました。

関連記事