C++における多態性
[第]31[課では]継承について学びました――[派生クラスが]基底クラスの[コードを][再利用]する仕組みです。
[しかし実際の場面では]、[baseクラスのポインタを使って、異なる派生クラスのオブジェクトを統一的に処理する必要がある]。
[これこそが]ポリモーフィズム――「[1つの]インターフェース、[複数の実装]」。
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 << "Animal speaks" << 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(); // ✅ polymorphism:calling
}
return 0;
}
2. 仮想関数(virtual)
基本的な使い方
[基底クラス]内で[関数]を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 << " makes a sound." << std::endl;
}
//!
virtual ~Animal() {}
};
class Dog : public Animal {
public:
Dog(const std::string& n) : Animal(n) {}
void speak() override {
std::cout << name << "says:!" << std::endl;
}
};
int main() {
Animal* a = new Dog("Buddy");
a->speak(); // ✅ output:Buddy says:!
delete a; // ✅ calling Dog 'sdestructor,calling Animal 's
return 0;
}
⚠️ [重要]: 基底クラスに仮想関数が[ある]場合、デストラクタも[明示的に宣言する]必要があります virtual![そうしないと] delete 基底クラスのポインタを経由して派生クラスのデストラクタが[呼び出されません]。
(2) 2.2 [キーワード]のオーバーライド(C++11)
C++11では、overrideという[キーワード]が[導入され]、これによりコンパイラが[仮想関数が実際にオーバーライドされているかどうかを]チェックしてくれるようになりました。
class Dog : public Animal {
public:
// ✅ override,compilation
void speak() override { ... }
// ❌ write( void speak(int x)),compilation
// void speak(int x) override { ... } //:
};
💡 [アドバイス]: [常に] overrideを追加——[コンパイラに]バグの検出を[任せる]。
3. 純粋仮想関数と抽象クラス
(1) 3.1 純粋仮想関数とは何か?
純粋仮想関数とは、基底クラス内で[宣言のみ行われ、実装は行われない]仮想関数のことであり、= 0で[マーク]されます。
class Animal {
public:
// ...
virtual void speak() = 0;
virtual ~Animal() {}
};
[純粋仮想関数を持つ]クラスは[「抽象クラス」と呼ばれる]——[インスタンス化はできず]、[基底クラスとしてのみ使用できる]。
Animal a; // ❌ compilation:
Animal* p = new Dog("Buddy"); // ✅ Usingpointer
(2) 3.2 なぜ純粋仮想関数が必要なのか?
[派生クラス]は[この]関数を[必ず][オーバーライド]**しなければなりません。
class Animal {
public:
virtual void speak() = 0; // ...
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "!" << std::endl;
}
// ✅ write speak(),compilation
};
4. 仮想関数[の]仕組み([選択科目])
(1) 4.1 仮想関数[表](vtable)
C++ [用語]仮想関数[表](vtable)[実装]多態性:
- [仮想関数]を持つクラスに対して、コンパイラは
vtable([仮想関数のアドレスを格納する]テーブル)を生成する。 - [各]オブジェクト[には、隠された]
vptr([vtableを]指す[ポインタ])が含まれている - [仮想関数を呼び出す際]、
vptr[を通じて] [正しい]関数アドレスを特定する
💡 [ヒント]: [これが、仮想関数を持つ]クラスやオブジェクトが[ポインタ分のサイズ](64[ビットシステムでは]8[バイト])を余分に占める理由です。
❓ よくある質問
Q:[なぜ]仮想関数だけでなく、デストラクタも[仮想でなければならない]のですか? A: [もし]
delete[が] 派生クラスのオブジェクト[を指す] 基底クラスのポインタを[削除した]場合、[かつ] 基底クラスの[デストラクタが] [仮想でない] 場合、[その際は] 基底クラスの[デストラクタのみが呼び出され]、派生クラスの[リソースがリークしてしまう]。CPPAnimal* a = new Dog("Buddy"); delete a; // ❌ [もし] ~Animal() が仮想関数でなければ、Dog のデストラクタは呼び出されない[黄金律]: 基底クラスに[仮想関数]がある場合、
~base class()[必ず]virtualでなければならない。
Q:override [と] final [の違いは]? A:> -
override:コンパイラに、この関数が基底クラスの仮想関数をオーバーライドしていることを伝える(コンパイラにチェックを任せる) > -final:コンパイラに、この関数はオーバーライドできないことを伝える(派生クラス内で使用する場合)CPPclass Dog : public Animal { public: void speak() override final { ... } // final:派生クラスでは[speak()を再定義できない] };
Q:[いつ]ポリモーフィズムを使うべきですか?** A:[異なる]型のオブジェクトを[統一された]インターフェースで[扱う]必要がある[とき]です。
[代表的なシナリオ]:
- [ゲーム]:[全キャラクター]の継承[元]
Character、[用]Character*[一元管理]- GUI:[すべてのコントロール]の継承[自]
Widget、[用]Widget*[統一描画]- ファイル[システム]:[すべて]fileinheritance[自]
File、[用]File*[統一読み書き]
📖 まとめ
- ポリモーフィズム[により]基底クラスのポインタが[派生クラスの]正しい実装を呼び出せる
- 基底クラス[内部用]
virtual[宣言]仮想関数 - 派生クラス[内部用]
override[オーバーライド]([コンパイラに]チェックを[任せる]) - [仮想関数を持つ]クラス、デストラクタも[そうである必要がある]
virtual - 純粋仮想関数(
= 0)[派生クラスに][必ずオーバーライドさせる] - [純粋仮想関数を持つ]クラスは抽象クラスであり、[インスタンス化]することは[できない]
📝 練習問題
-
初心者(難易度 ⭐): [定義する]
Vehicle基底クラス。このクラスには、[純粋仮想関数]void move()が含まれている。CarとBicycleが [これを] 継承し、[それぞれ] 「[車が道路を走行している]」と「[自転車が自転車道を走行している]」を出力するようにする。 -
中級(難易度 ⭐⭐): [上記の]プログラムを[拡張]し、
void stop()という仮想関数を[追加]して、Carが「[車が路肩に停車]」と出力し、Bicycleが「[自転車がブレーキをかける]」と出力するように[する]。 -
上級(難易度 ⭐⭐⭐): [[従業員給与システム]の設計:]
-
Employee[抽象]基底クラス([氏名]、[社員番号]、[給与計算]virtual double calcSalary() = 0)
FullTimeEmployee[正社員]([月給])PartTimeEmployee[パートタイム]([時給] × [労働時間])- [使用]
vector<Employee*>[全従業員を保存]、[一括呼び出し]calcSalary()
- 仮想関数:基底クラス[宣言]、派生クラス[オーバーライド]、実行時多態性
- virtual [キーワード宣言]仮想関数
- override [正しく上書きされているか確認]
- 純粋仮想関数[および]抽象クラス[はインスタンス化できない]
- 仮想関数[表](vtable)[実装]実行時多態性
6. 🚀 次は
[ポリモーフィズムを学びました]。[次は] テンプレート(Templates)を学びます([第]33[回])―― [関数や]クラス[が任意の]型に対応できるようにし、[真の]「ジェネリック[プログラミング]」を実現しましょう!



