はじめに
C++の**継承 (Inheritance)**は、オブジェクト指向の三本柱の一つであり、コードの再利用性を高め、拡張性の高い階層構造を構築するための強力な機能です。
この記事では、C++の継承にまつわる以下の主要な概念を、RPGのキャラクターを例に、網羅的に解説します。
- 基本的な継承: 親の性質を引き継ぐ。
- 仮想関数とオーバーライド: 親の機能を子が好きに書き換える。
- 純粋仮想関数と抽象クラス: 子に特定の機能の実装を強制する。
- 多重継承: 複数の親から性質を引き継ぐ。
1. 基本的な継承
継承とは、あるクラス(親クラスまたは基底クラス)の性質(メンバ)を引き継いで、新しいクラス(子クラスまたは派生クラス)を定義することです。
サンプルコード
#include <iostream>
#include <string>
using namespace std;
// 親クラス
class Character {
private:
string name_;
int hp_;
public:
Character(string name, int hp) : name_(name), hp_(hp) {}
const string& getName() const { return name_; }
int getHp() const { return hp_; }
};
// Characterクラスを継承した子クラス
class Warrior : public Character {
private:
int strength_;
public:
// 親クラスのコンストラクタを呼び出す
Warrior(string name, int hp, int strength)
: Character(name, hp), strength_(strength) {}
int getStrength() const { return strength_; }
};
int main() {
Warrior arthur("アーサー", 150, 25);
// 親から継承した機能
cout << "名前: " << arthur.getName() << endl;
// 子独自の機能
cout << "腕力: " << arthur.getStrength() << endl;
return 0;
}
解説: Warrior
クラスは、Character
クラスをpublic
に継承することで、getName()
やgetHp()
といったpublic
なメンバを、あたかも自分自身のメンバであるかのように利用できます。
2. 仮想関数とオーバーライド
オーバーライドとは、親クラスのメンバ関数を、子クラスで処理内容を上書きすることです。そして、親クラスのポインタや参照経由で呼び出された場合でも、正しく子クラスのバージョンが呼ばれるようにするのが仮想関数 (virtual
) です。
キーワード | 役割 |
virtual | 親クラス側で、オーバーライドされる可能性がある関数に付ける。 |
override | 子クラス側で、親の関数をオーバーライドしていることを明示する。 |
final | これ以上、子クラスでのオーバーライドを禁止する。 |
Google スプレッドシートにエクスポート
サンプルコード
struct Base {
virtual void show() const { cout << "Base" << endl; }
};
struct Derived : Base {
// Base::show()をオーバーライドしていることを明示
void show() const override { cout << "Derived" << endl; }
};
解説: override
を付けておくと、もし親クラスに対応する仮想関数がなかった場合にコンパイルエラーとなるため、意図しないオーバーロードなどを防ぐことができ、コードが安全になります。
3. 純粋仮想関数と抽象クラス
純粋仮想関数とは、実装を持たない(= 0;
と記述する)特殊な仮想関数です。純粋仮想関数を一つでも持つクラスは抽象クラスとなり、オブジェクトを直接作成できなくなります。
抽象クラスを継承した子クラスは、親の純粋仮想関数を全てオーバーライドしない限り、自身も抽象クラスになります。
サンプルコード
// 抽象クラス
struct Shape {
virtual ~Shape() {}
// 純粋仮想関数: 「図形は描画できなければならない」というルールを定義
virtual void draw() const = 0;
};
// 子クラスは、draw()を必ず実装しなければならない
struct Circle : Shape {
void draw() const override { cout << "円を描画" << endl; }
};
struct Square : Shape {
void draw() const override { cout << "四角形を描画" << endl; }
};
int main() {
// Shape s; // エラー! 抽象クラスはインスタンス化できない
Circle c;
c.draw();
return 0;
}
解説: 抽象クラスは、子クラスに特定の機能の実装を強制するための「インターフェース」としての役割を果たします。
4. 多重継承
C++では、クラスは複数の親クラスを持つことができます。これを多重継承と呼びます。
サンプルコード
#include <iostream>
struct CanFly { void fly() { std::cout << "飛んでいます。" << std::endl; } };
struct CanSwim { void swim() { std::cout << "泳いでいます。" << std::endl; } };
// CanFlyとCanSwimの両方を継承
struct FlyingFish : CanFly, CanSwim {};
int main() {
FlyingFish ff;
ff.fly();
ff.swim();
return 0;
}
解説: FlyingFish
クラスは、CanFly
とCanSwim
の両方の機能(fly
, swim
)を引き継ぎます。多重継承は強力ですが、菱形継承問題など複雑な問題を引き起こす可能性もあるため、注意深く使う必要があります。
まとめ
今回は、C++の継承に関する主要な概念を網羅的に解説しました。これらの機能を適切に使い分けることで、再利用性が高く、拡張しやすく、かつ安全なクラス階層を設計することができます。