【C++】仮想関数 (virtual) とは?ポリモーフィズム(多態性)を実現する方法

目次

はじめに

C++の継承では、親クラス(基底クラス)へのポインタを使って、子クラス(派生クラス)のオブジェクトを指し示すことができます。しかし、ここで一つ問題があります。

親クラスのポインタ->関数() のように呼び出した場合、通常は**ポインタの型(親クラス)**で定義された関数が呼び出されてしまいます。たとえ子クラスでその関数がオーバーライドされていても、子クラス版の関数は呼ばれません。

この問題を解決し、ポインタが実際に指しているオブジェクトの種類に応じて、適切なバージョンの関数を呼び出す仕組みが「仮想関数 (virtual function)」であり、それによって実現される性質が、オブジェクト指向の三本柱の一つ「多態性(ポリモーフィズム)」です。


仮想関数を使ったサンプルコード

このコードは、Character(親クラス)と、それを継承した Warrior, Mage(子クラス)を定義します。attack 関数を仮想関数として定義し、各クラスでオーバーライドします。

完成コード

#include <iostream>
#include <string>
#include <vector>

using namespace std;

// --- 親クラス(基底クラス) ---
class Character {
protected:
    string name;
public:
    Character(string n) : name(n) {}
    
    // 1. virtualを付けて、仮想関数として宣言
    virtual void attack() {
        cout << name << " の攻撃!...しかし何も起こらなかった。" << endl;
    }
};

// --- 子クラス(派生クラス) ---
class Warrior : public Character {
public:
    // 親クラスのコンストラクタを呼び出す
    Warrior(string n) : Character(n) {}

    // 2. 親クラスの仮想関数をオーバーライド
    void attack() {
        cout << name << " は剣を振った!" << endl;
    }
};

class Mage : public Character {
public:
    Mage(string n) : Character(n) {}
    
    // 2. 親クラスの仮想関数をオーバーライド
    void attack() {
        cout << name << " は魔法を唱えた!" << endl;
    }
};


int main() {
    // 3. 親クラスへのポインタの配列(vector)を作成
    vector<Character*> party;
    
    party.push_back(new Warrior("戦士"));
    party.push_back(new Mage("魔法使い"));

    cout << "--- パーティの全メンバーが攻撃! ---" << endl;
    
    // 4. 同じ呼び出し方でも、オブジェクトの種類によって動作が変わる
    for (Character* p_char : party) {
        p_char->attack(); // ポインタ経由でattack()を呼び出し
    }

    // メモリの解放
    for (Character* p_char : party) {
        delete p_char;
    }
    
    return 0;
}

実行結果

--- パーティの全メンバーが攻撃! ---
戦士 は剣を振った!
魔法使い は魔法を唱えた!

コードの解説

1. virtual void attack()

親クラス Characterattack 関数の宣言の先頭に virtual を付けています。これにより、attack 関数は仮想関数となります。

2. void attack() (子クラス内)

子クラス WarriorMage で、それぞれ独自の attack 関数をオーバーライドしています。

3. 親クラスへのポインタの配列 (vector<Character*>)

party という vector(動的配列)は、Character クラスへのポインタを格納します。ここに、Warrior オブジェクトのアドレスと Mage オブジェクトのアドレスを入れています。(new を使って動的にオブジェクトを生成しています)

4. p_char->attack()

これが、**多態性(ポリモーフィズム)**が機能している核心部分です。

  • ループ変数 p_charは、常に Character*(親クラスへのポインタ)です。
  • にもかかわらず、p_char->attack() を呼び出すと、
    • p_charWarrior オブジェクトを指しているときは、Warrior 版の attack() が実行されます。
    • p_charMage オブジェクトを指しているときは、Mage 版の attack() が実行されます。

これは、attack 関数が仮想関数であるため、C++コンパイラが「ポインタの型ではなく、ポインタが実際に指しているオブジェクトの型を見て、呼び出すべき最適な関数を、プログラムの実行時に決定する」という特別な動作(動的ディスパッチ)を行っているからです。

もし、親クラスの attack 関数から virtual キーワードを外してしまうと、実行結果は2回とも「〜の攻撃!…しかし何も起こらなかった。」となり、子クラスのオーバーライドした関数は呼ばれません。


まとめ

今回は、C++の仮想関数と、それによって実現される多態性(ポリモーフィズム)について解説しました。

  • 親クラスで、子クラスにオーバーライドされる可能性のある関数には virtual を付ける。
  • virtual を付けた関数(仮想関数)は、親クラスへのポインタ経由で呼び出された場合でも、ポインタが実際に指しているオブジェクトの型でオーバーライドされた関数が実行される。
  • これにより、同じインターフェース(呼び出し方)で、異なるオブジェクトに異なる振る舞いをさせることができる(多態性)。

仮想関数は、オブジェクト指向の柔軟性と拡張性を支える、非常に重要な仕組みです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次