C++の抽象クラス(abstract class)とは、共通のインターフェースだけを定義して、実際の処理は子クラスに任せるためのクラスです。オブジェクト指向における「型の共通化」や「設計の基盤」に使われます。
抽象クラスは「インスタンスを生成できないクラス」です。少なくとも1つ以上の純粋仮想関数(pure virtual function)を含みます。
関数に = 0 を付けると、その関数は純粋仮想関数になります(戻り値が0という意味ではありません)。
// 20_abstract_base.cpp
#include <iostream>
using namespace std;
class Enemy {
public:
// 純粋仮想関数:派生クラスで必ず実装する必要がある
virtual void attack() = 0;
};
class Slime : public Enemy {
public:
void attack() override {
cout << "スライムの体当たり!" << endl;
}
};
class Goblin : public Enemy {
public:
void attack() override {
cout << "ゴブリンの斬撃!" << endl;
}
};
int main() {
// Enemy e; // エラー:抽象クラスはインスタンス生成できない
Enemy* e1 = new Slime();
Enemy* e2 = new Goblin();
e1->attack();
e2->attack();
delete e1;
delete e2;
}
実行結果(例)
スライムの体当たり! ゴブリンの斬撃!
virtual を付けただけの関数なら、派生クラスで定義しなくても構いません。ただし、純粋仮想関数(= 0 を付けたもの)の場合は、派生クラスで定義しないとコンパイルエラーになります。純粋仮想関数は「中身を持たない関数の宣言だけ」です。これにより、派生クラスに「この関数を必ず定義しなければならない」というルールを課すことができます。
virtual void attack() = 0; // 実装を持たない、命令だけの関数
| メリット | 内容 |
|---|---|
| 統一されたインターフェース | 共通の基底クラスで扱えるため、コードが整理される |
| 拡張性 | 新しい敵キャラを追加しても、共通のポインタで操作可能 |
| 安全性 | 関数の実装忘れをコンパイル時に検出できる |
抽象クラスと純粋仮想関数を使うと、異なる型のオブジェクトを同じインターフェースで扱うことができます。これは「ポリモーフィズム(多態性)」の代表的な例です。
// 20_polymorphism.cpp
#include <iostream>
#include <vector>
using namespace std;
class Enemy {
public:
virtual void attack() = 0; // 純粋仮想関数
virtual ~Enemy() {} // 仮想デストラクタ
};
class Slime : public Enemy {
public:
void attack() override { cout << "スライムの体当たり!" << endl; }
};
class Goblin : public Enemy {
public:
void attack() override { cout << "ゴブリンの斬撃!" << endl; }
};
int main() {
vector<Enemy*> enemies;
enemies.push_back(new Slime());
enemies.push_back(new Goblin());
for (Enemy* e : enemies) {
e->attack(); // 型ごとに異なるattack()が呼ばれる(多態性)
}
for (Enemy* e : enemies) delete e;
}
実行結果(例)
スライムの体当たり! ゴブリンの斬撃!
Enemy*)で複数の型を一括管理できます。これはポリモーフィズム(多態性)の応用例でもあります。Enemy を抽象クラスとして定義し、派生クラスで attack() を実装します。
// 20_abstract_base.cpp
#include <iostream>
#include <vector>
using namespace std;
class Enemy {
public:
virtual void attack() = 0; // 純粋仮想関数
virtual ~Enemy() {}
};
class Slime : public Enemy {
public:
void attack() override {
cout << "スライムの体当たり!" << endl;
}
};
class Goblin : public Enemy {
public:
void attack() override {
cout << "ゴブリンの斬撃!" << endl;
}
};
int main() {
vector<Enemy*> enemies;
enemies.push_back(new Slime());
enemies.push_back(new Goblin());
for (Enemy* e : enemies) {
e->attack();
}
for (Enemy* e : enemies) delete e;
return 0;
}
20_01_Abstract の 20_abstract_base.cpp をコピーして 20_02_AbstractMod に貼り付け、次の変更を加えてみましょう:
Dragon クラスを追加し、attack() で「ドラゴンの炎ブレス!」と表示するEnemy* e = new Enemy(); の行を書いてコンパイルエラーを確認する(その後コメントアウト)speak() という純粋仮想関数を Enemy にも追加し、各クラスで実装するvirtual void 関数() = 0; で純粋仮想関数を定義できる。次のコードのうち、コンパイルエラーになるものはどれか。
class Shape {
public:
virtual void draw() = 0;
};
class Circle : public Shape {
public:
void draw() override { /* 描画処理 */ }
};
Shape は純粋仮想関数を持つ抽象クラスなので、直接インスタンス化(Shape s;)はコンパイルエラーになる。ポインタ型(Shape*)での宣言は問題ない。
純粋仮想関数の宣言として正しいものはどれか。
virtual キーワードと = 0 の両方が必要。virtual なしでは機能しない。C++に abstract キーワードはない。
抽象クラスを使う最大のメリットとして最も適切なものはどれか。
次のコードを実行したとき、出力はどれか。
#include <iostream>
#include <vector>
using namespace std;
class Enemy {
public:
virtual void attack() = 0;
virtual ~Enemy() {}
};
class Slime : public Enemy {
public:
void attack() override { cout << "A" << endl; }
};
class Goblin : public Enemy {
public:
void attack() override { cout << "B" << endl; }
};
int main() {
vector<Enemy*> v = { new Slime(), new Goblin(), new Slime() };
for (Enemy* e : v) { e->attack(); delete e; }
}
vector の要素順に Slime, Goblin, Slime が格納されているので、A・B・A が各改行付きで出力される。純粋仮想関数によるポリモーフィズムの動作。