多態(ポリモーフィズム)とは、同じインターフェースを用いて異なるオブジェクトがそれぞれ固有の振る舞いを実現する仕組みである。たとえば、乗車券の購入という動作を「子供」と「大人」の両方に対して行う場合、同じメソッド呼び出しでも価格処理が異なり、これが多態の本質である。
虚関数とそのオーバーライド
多態を実現するには、基底クラスの仮想関数を派生クラスで再定義(オーバーライド)し、基底クラスへのポインタまたは参照経由で呼び出す必要がある。
class Person {
public:
virtual void purchaseTicket() const {
std::cout << "通常料金" << std::endl;
}
virtual ~Person() = default; // 基底クラスのデストラクタは常にvirtualに
};
class Child : public Person {
public:
void purchaseTicket() const override {
std::cout << "半額料金" << std::endl;
}
};
void processTicketPurchase(Person* user) {
user->purchaseTicket(); // 実行時の型に基づいて動的ディスパッチ
}
int main() {
Person adult;
Child child;
processTicketPurchase(&adult); // 出力: 通常料金
processTicketPurchase(&child); // 出力: 半額料金
return 0;
}
ここで virtual キーワードは、関数の動的バインディングを可能にする。override 修飾子は明示的にオーバーライドを意図していることをコンパイラに伝え、誤ったシグネチャによる隠蔽を防ぐ。
関数の3種類の関係:オーバーロード、オーバーライド、隠蔽
| 項目 | オーバーロード | オーバーライド | 隠蔽 |
|---|---|---|---|
| 作用域 | 同一スコープ | 基底・派生クラス | 基底・派生クラス |
| 関数名 | 同一 | 同一 | 同一 |
| パラメータ | 異なる | 同一 | 異なるか同一 |
| virtual 必須 | いいえ | はい | いいえ |
| 動的ディスパッチ | なし | あり | なし |
例として、派生クラスで基底クラスの非仮想関数を同じ名前で再定義すると、それは「隠蔽」であり、基底クラスの関数はアクセスできなくなる。
class Base {
public:
void show() { std::cout << "Base show" << std::endl; }
};
class Derived : public Base {
public:
void show(int x) { std::cout << "Derived show: " << x << std::endl; }
// Base::show() は隠蔽される。Base::show() は直接呼び出せない
};
抽象クラスと純粋仮想関数
純粋仮想関数は、= 0 で宣言され、実装を必須としない。この関数を含むクラスは抽象クラスとなり、直接インスタンス化できない。派生クラスはすべての純粋仮想関数を実装しなければ、自身も抽象クラスのままとなる。
class Vehicle {
public:
virtual void startEngine() const = 0; // 純粋仮想関数
virtual ~Vehicle() = default;
};
class Sedan : public Vehicle {
public:
void startEngine() const override {
std::cout << "セダンのエンジンを始動" << std::endl;
}
};
class Truck : public Vehicle {
public:
void startEngine() const override {
std::cout << "トラックのエンジンを始動" << std::endl;
}
};
void operateVehicle(Vehicle* v) {
v->startEngine(); // 動的ディスパッチにより派生クラスの実装が呼び出される
}
int main() {
Sedan car;
Truck truck;
operateVehicle(&car); // セダンの実装
operateVehicle(&truck); // トラックの実装
// Vehicle v; // エラー:抽象クラスはインスタンス化不可
return 0;
}
抽象クラスは、インターフェースの契約として機能し、派生クラスに一貫した振る舞いを強制する。これにより、システムの拡張性と保守性が大幅に向上する。