©️

C++ 仮想関数・多態性(ポリモーフィズム)

2024/10/17に公開

多態性(ポリモーフィズム)

  • ひとつの共通のインターフェースを使って複数の型(クラス) のオブジェクトを扱える
    • 共通のインターフェース:抽象クラス(基底クラス)
    • 呼び出す複数の型:具象クラス(派生クラス)

多態性を実現する要素

抽象クラス

  • 少なくとも1つの純粋仮想関数を持つクラス
  • 他のクラスに継承されるための基盤として機能
  • 主にインターフェースを定義する役割を担い、具象的な実装は具象クラス(派生クラス)に任せる
  • インスタンスを生成することはできない(コンパイルエラー)

抽象クラスの構成要素

仮想関数

  • 基底クラス(抽象クラス)で具体的な実装を持ちつつ、
    派生クラスでオーバーライドが可能なメソッド
  • virtualキーワードを使う
  • 仮想関数を使うと、
    基底クラスのポインタや参照を通じて派生クラスのメソッドを呼ぶ際、
    実行時にどのメソッドを呼び出すかが決定される(動的バインディング
  • 仮想関数を使わない場合、
    コンパイル時にどのメソッドを呼び出すかが決定される(静的バインディング

純粋仮想関数

  • 具体的な実装を持たない仮想関数
  • = 0する

仮想デストラクタ

  • 基底クラスのポインタで派生クラスのオブジェクトを指している場合、正しいデストラクタが呼び出される必要がある
  • メモリリークを防ぐために仮想デストラクタにする
基底クラスのポインタで派生クラスのオブジェクトを指している例
class Base {
public:
    // 仮想デストラクタ
    virtual ~Base() {
        std::cout << "Base destructor called" << std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        std::cout << "Derived destructor called" << std::endl;
    }
};

int main() {
    // 基底クラスのポインタで派生クラスのオブジェクトを指している
    Base* ptr = new Derived();
    delete ptr; // ここで仮想デストラクタが呼び出される
    return 0;
}

実行結果

Derived destructor called
Base destructor called

抽象クラスのサンプルコード

class Animal {
public:
    // 純粋仮想関数: 基底クラスには実装がない
    virtual void makeSound() = 0;
    // 仮想デストラクタ: 派生クラスのデストラクタが正しく呼ばれるようにするため
    virtual ~Animal() {}
};

具象クラス

  • 抽象クラスを継承して新しい機能や動作を追加するクラス
  • 抽象クラスの純粋仮想関数を実装する
  • インスタンスを生成できる

具象クラスのサンプルコード

class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "ワンワン!" << std::endl;
    }
};

多態性のサンプルコード

#include <iostream>

// 基底クラス(抽象クラス)
class Animal {
public:
    // 純粋仮想関数:派生クラスで必ずオーバーライドさせる
    virtual void speak() const = 0;

    // 仮想関数:派生クラスでオーバーライドしても良い
    virtual void eat() const {
        std::cout << "Animal is eating" << std::endl;
    }

    virtual ~Animal() {}  // 仮想デストラクタ
};

// 派生クラス1:Dog
class Dog : public Animal {
public:
    // 純粋仮想関は必ずオーバーライドする
    void speak() const override {
        std::cout << "Dog barks" << std::endl;
    }
    // 仮想関数をオーバーライドする
    void eat() const override {
        std::cout << "Dog is eating" << std::endl;
    }
};

// 派生クラス2:Cat
class Cat : public Animal {
public:
    // 純粋仮想関は必ずオーバーライドする
    void speak() const override {
        std::cout << "Cat meows" << std::endl;
    }

    // eat()はオーバーライドしない(基底クラスの実装が使われる)
};

int main() {
    // 基底クラスのポインタを使って、派生クラスのオブジェクトを指す
    Animal* animal1 = new Dog();  // Dogオブジェクト
    Animal* animal2 = new Cat();  // Catオブジェクト

    // 多態性を利用して、動的(実行時)に呼び出されるメソッドが決まる
    animal1->speak();  // Dogのspeak()が呼ばれる
    animal2->speak();  // Catのspeak()が呼ばれる

    // 仮想関数eat()の呼び出し
    animal1->eat();  // Dogのeat()が呼ばれる
    animal2->eat();  // Catのeat()は基底クラス(Animal)の実装が呼ばれる

    // メモリの解放
    delete animal1;
    delete animal2;

    return 0;
}

基底クラス Animal のポインタや参照を使って、派生クラスのオブジェクト (Dog や Cat) のメソッドを呼び出すことができる

実行結果

Dog barks
Cat meows
Dog is eating
Animal is eating

Discussion