はじめに
C++の継承において、親クラス(基底クラス)の段階では具体的な処理内容は決められないが、「このクラスを継承する子クラス(派生クラス)は、必ずこの機能(メンバ関数)を実装しなければならない」というルールを設けたい場合があります。
例えば、「図形」という親クラスには「面積を計算する (getArea
)」という機能が必要ですが、「図形」というだけでは具体的な計算方法は分かりません。「円」や「四角形」といった子クラスになって初めて、具体的な計算式が決まります。
このような「機能の枠組みだけを親クラスで定義し、具体的な実装を子クラスに強制する」仕組みが、純粋仮想関数と抽象クラスです。
抽象クラスと純粋仮想関数のサンプルコード
このコードは、Character
(キャラクター)という抽象クラスを定義します。attack
という純粋仮想関数を持つことで、「キャラクターは攻撃手段を持つべきだ」というルールを定めます。
完成コード
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// --- 1. 抽象クラス(基底クラス) ---
class Character {
protected:
string name;
public:
Character(string n) : name(n) {}
// 2. 純粋仮想関数 "= 0" を付けて、実装を持たないことを示す
virtual void attack() = 0;
};
// --- 3. 派生クラス1 ---
class Warrior : public Character {
public:
Warrior(string n) : Character(n) {}
// 4. 純粋仮想関数をオーバーライドして、具体的な処理を実装
void attack() {
cout << name << " は剣で攻撃した!" << endl;
}
};
// --- 3. 派生クラス2 ---
class Mage : public Character {
public:
Mage(string n) : Character(n) {}
// 4. 純粋仮想関数をオーバーライドして、具体的な処理を実装
void attack() {
cout << name << " は魔法を唱えた!" << endl;
}
};
int main() {
// NG: 抽象クラスのオブジェクトは作成できない!
// Character genericChar("ただのキャラ"); // コンパイルエラー!
// 基底クラスへのポインタを使って、派生クラスのオブジェクトを扱う
vector<Character*> party;
party.push_back(new Warrior("戦士"));
party.push_back(new Mage("魔法使い"));
for (Character* p_char : party) {
p_char->attack(); // ポリモーフィズムが機能し、それぞれのattackが呼ばれる
}
// メモリの解放
for (Character* p_char : party) {
delete p_char;
}
return 0;
}
コードの解説
1. 抽象クラス (Character
)
一つ以上の純粋仮想関数を持つクラスは、自動的に抽象クラスになります。抽象クラスは、それ自身のオブジェクトを直接作成することができません(new Character()
などはコンパイルエラー)。抽象クラスは、あくまで他のクラスに継承されるための「不完全な設計図」としての役割に特化します。
2. 純粋仮想関数 (virtual void attack() = 0;
)
virtual
: この関数が、子クラスでオーバーライドされることを示します。= 0
: この構文が、この関数が純粋仮想関数であることを示します。これは、「この関数には実装(処理内容)がありません。子クラスが必ず実装してください」という意味です。
3. 派生クラス (Warrior
, Mage
)
抽象クラス Character
を継承した子クラスは、親クラスが持つ全ての純粋仮想関数を、必ずオーバーライドして実装しなければならないというルールが課せられます。もし Warrior
クラスが attack
関数を実装しなかった場合、Warrior
クラス自身も抽象クラスとなり、オブジェクトを作成できなくなります。
4. ポリモーフィズムの実現
main
関数では、Character*
という親クラスへのポインタを使って、Warrior
と Mage
という異なる型の子クラスオブジェクトを統一的に扱っています。
p_char->attack()
を呼び出すと、attack
が仮想関数であるため、ポリモーフィズム(多態性)が機能し、ポインタが実際に指しているオブジェクトの型(Warrior
なのか Mage
なのか)に応じて、それぞれの attack
関数が正しく呼び分けられます。
まとめ
今回は、C++の抽象クラスと純粋仮想関数について解説しました。
- 純粋仮想関数 (
virtual ... = 0;
): 実装を持たず、子クラスに実装を強制するための関数。 - 抽象クラス: 純粋仮想関数を一つでも持つクラス。オブジェクトを直接作成できない。継承されるための「設計図」の役割。
この仕組みは、オブジェクト指向において「インターフェースの統一」という非常に重要な役割を果たします。「『キャラクター』というカテゴリに属するものは、必ず『攻撃』できなければならない」といった、プログラム全体の設計ルールを厳密に定義し、一貫性を保つために不可欠な機能です。