C++: C++ ポリモーフィズム
最終更新:2026-08-31
レッスン31では継承を学びました —— 派生クラスが基底クラスのコードを再利用。
でも、基底クラスのポインタで派生クラスのオブジェクトを指すことができます。
ポリモーフィズムで解決 —— 「インターフェース、多様な実装」。
1. ポリモーフィズムとは?
(1) 1.1 ポリモーフィズムの概念
| 概念 | プログラムでの意味 |
|---|---|
| 「インターフェース、多様な実装」 | 基底クラスのポインタで speak() を呼び出し、派生クラスの実装を実行 |
| 「実行時、オブジェクトに応じた振る舞い」 | 関数呼び出し時に、オブジェクトの実際の型に応じた振る舞い |
ポリモーフィズムの目的: 基底クラスのポインタで派生クラスのオブジェクトを指し、仮想関数を呼び出すと動的バインディングで派生クラスの実装を実行。
(2) 1.2 なぜポリモーフィズムが必要?
ポリモーフィズムを使わない場合(オブジェクト指向プログラミングの弱点):
▶ サンプル 2:オブジェクト指向プログラミング(難易度 ⭐)
#include <iostream>
class Dog {
public:
void speak() { std::cout << "ワン!" << std::endl; }
};
class Cat {
public:
void speak() { std::cout << "ニャー!" << std::endl; }
};
int main() {
Dog* d = new Dog();
Cat* c = new Cat();
d->speak();
c->speak();
// ❌ 統一的なインターフェースがない
return 0;
}
ポリモーフィズムを使う場合(統一的インターフェース):
#include <iostream>
class Animal {
public:
virtual void speak() { std::cout << "動物が鳴く" << std::endl; }
};
class Dog : public Animal {
public:
void speak() override { std::cout << "ワン!" << std::endl; }
};
class Cat : public Animal {
public:
void speak() override { std::cout << "ニャー!" << std::endl; }
};
int main() {
Animal* animals[] = {new Dog(), new Cat()};
for (int i = 0; i < 2; i++) {
animals[i]->speak(); // ✅ ポリモーフィズム:適切なspeakを呼び出し
}
return 0;
}
2. 仮想関数(virtual)
(1) 2.1 基本的な使い方
基底クラスの関数に virtual を付けると、派生クラスで override(オーバーライド)できます。
#include <iostream>
#include <string>
class Animal {
protected:
std::string name;
public:
Animal(const std::string& n) : name(n) {}
// 仮想関数
virtual void speak() {
std::cout << name << "が鳴く。" << std::endl;
}
// 仮想デストラクタ!
virtual ~Animal() {}
};
class Dog : public Animal {
public:
Dog(const std::string& n) : Animal(n) {}
void speak() override {
std::cout << name << "が吠える:ワン!" << std::endl;
}
};
int main() {
Animal* a = new Dog("Buddy");
a->speak(); // ✅ 出力:Buddyが吠える:ワン!
delete a; // ✅ Dogのデストラクタを呼び出し、その後Animalのデストラクタ
return 0;
}
⚠️ 重要: 基底クラスに仮想関数がある場合、デストラクタも virtual にする! delete 基底クラスポインタで派生クラスのデストラクタを呼び出せる。
(2) 2.2 override(C++11)
C++11の override キーワードで、コンパイラに仮想関数をオーバーライドすることを明示。
class Dog : public Animal {
public:
// ✅ 正しくオーバーライド、コンパイル成功
void speak() override { ... }
// ❌ 間違ったオーバーライド(例:void speak(int x))、コンパイルエラー
// void speak(int x) override { ... } // エラー:基底クラスにない
};
override を必ず付ける —— コンパイラがバグを検出。
3. 純粋仮想関数と抽象クラス
(1) 3.1 純粋仮想関数とは?
純粋仮想関数は基底クラスで実装がない仮想関数、= 0 を使う。
class Animal {
public:
// 純粋仮想関数
virtual void speak() = 0;
virtual ~Animal() {}
};
純粋仮想関数があるクラスは「抽象クラス」 —— インスタンス化できない、基底クラスとして使う。
Animal a; // ❌ コンパイルエラー:抽象クラスはインスタンス化不可
Animal* p = new Dog("Buddy"); // ✅ ポインタで使う
(2) 3.2 なぜ純粋仮想関数が必要?
派生クラスに必ず実装させる関数を定義。
class Animal {
public:
virtual void speak() = 0; // 派生クラスで必ず実装
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "ワン!" << std::endl;
}
// ✅ speak()を実装、コンパイル成功
};
4. 仮想関数の仕組み(初級はスキップ可)
(1) 4.1 仮想関数テーブル(vtable)
C++は仮想関数テーブル(vtable)でポリモーフィズムを実現:
- 仮想関数があるクラスでは、コンパイラが
vtable(仮想関数アドレスの表)を作成 - オブジェクトには隠しポインタ
vptr(vtableへのポインタ)がある - 仮想関数を呼び出すと、
vptr経由で関数アドレスを検索
5. 実践例:図形
▶ サンプル 1:ポリモーフィズムで図形の面積計算(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <cmath>
// 抽象基底クラス
class Shape {
public:
virtual double area() = 0;
virtual void printInfo() = 0;
virtual ~Shape() {}
};
// 円クラス
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
void printInfo() override {
std::cout << "円、半径 = " << radius
<< "、面積 = " << area() << std::endl;
}
};
// 長方形クラス
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override {
return width * height;
}
void printInfo() override {
std::cout << "長方形、" << width << " x " << height
<< "、面積 = " << area() << std::endl;
}
};
int main() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle(5.0));
shapes.push_back(new Rectangle(4.0, 6.0));
for (Shape* s : shapes) {
s->printInfo();
}
// メモリ解放
for (Shape* s : shapes) {
delete s;
}
return 0;
}
出力:
TEXT 📖 参照専用円、半径 = 5、面積 = 78.5397 長方形、4 x 6、面積 = 24
▶ サンプル 3:純粋仮想関数でインターフェース(難易度 ⭐⭐)
#include <iostream>
#include <string>
// 抽象インターフェース
class Drawable {
public:
virtual void draw() = 0;
virtual ~Drawable() = default;
};
class Circle : public Drawable {
private:
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override {
std::cout << "半径" << radius << "の円を描画" << std::endl;
}
};
class Square : public Drawable {
private:
double side;
public:
Square(double s) : side(s) {}
void draw() override {
std::cout << "辺" << side << "の正方形を描画" << std::endl;
}
};
int main() {
Drawable* shapes[] = {new Circle(5.0), new Square(4.0)};
for (Drawable* s : shapes) {
s->draw();
delete s;
}
return 0;
}
出力:
TEXT 📖 参照専用半径5の円を描画 辺4の正方形を描画
❓ よくある質問
delete 派生クラスオブジェクトの基底クラスポインタを使う場合、基底クラスのデストラクタが仮想でないと、基底クラスのデストラクタしか呼ばれず、派生クラスのデストラクタが呼ばれない。
Animal* a = new Dog("Buddy");
delete a; // ❌ ~Animal() が仮想でないと、Dogのデストラクタが呼ばれない
推奨:
📖 まとめ
- ポリモーフィズムは基底クラスのポインタで派生クラスの実装を呼び出す
- 基底クラスで
virtualで仮想関数を宣言 - 派生クラスで
overrideでオーバーライド(コンパイラがチェック) - 仮想関数があるクラスでは、デストラクタを
virtualにする - 純粋仮想関数(
= 0)で派生クラスに必ず実装させる - 純粋仮想関数があるクラスは抽象クラス、インスタンス化不可
📝 練習問題
-
初級(難易度 ⭐):
Vehicle基底クラスを定義し、純粋仮想関数void move()を持たせてください。CarとBicycleを継承し、「走る」と「進む」を出力。 -
中級(難易度 ⭐⭐): 上のプログラムを改良し、
void stop()仮想関数を追加、Carは「停止」、Bicycleは「ブレーキ」を出力。 -
上級(難易度 ⭐⭐⭐): 給与計算システムを実装:
Employee基底クラス(名前、基本給、virtual double calcSalary() = 0)FullTimeEmployee(基本給 × 12ヶ月)PartTimeEmployee(時給 × 働いた時間)vector<Employee*>で全従業員を管理、calcSalary()を呼び出し
6. 🚀 次のステップ
ポリモーフィズムを理解したら、次は テンプレート(レッスン33) —— 関数とクラスを型に依存しない、「ジェネリックプログラミング」を実現しよう!