今回は、C++での「継承」と「protected」について解説していきます。
「継承って何?クラスとどう違うの?」
「protectedってprivateとどう違うの?」
「virtualやoverrideって何のためにあるの?」
こんな疑問はありませんか?
継承は、前回のクラスの記事で作った Enemy クラスをさらに発展させる仕組みです。
「敵キャラの共通部分は親クラスにまとめて、種類ごとの違いは子クラスで書く」という設計が継承を使うとスッキリ実現できます。
この記事を読み終えると、あなたはC++の継承とprotectedの使い方をマスターできると思いますので、ぜひ最後まで読んでいただけると嬉しいです。
継承とは?
継承とは、
- 「既存のクラスの機能を引き継いで、新しいクラスを作る仕組み」
です。
引き継がれる元のクラスを基底クラス(親クラス)、引き継いで作った新しいクラスを派生クラス(子クラス)と呼びます。
たとえば、ゲームに「スライム」「ゴブリン」「ドラゴン」の3種類の敵がいるとします。
どの敵にも「HP」「ダメージを受ける処理」などの共通の機能があります。
これを毎回書くのは大変ですし、バグが増える原因になります。
そこで、共通の機能を Enemy(親)にまとめ、各敵(子)に引き継がせるのが継承です。
継承の基本の書き方
継承の書き方は以下の通りです。
class 派生クラス名 : public 基底クラス名 {
// 追加・上書きしたいメンバ
};実際に書いてみましょう。
▼enemy.h
#pragma once
#include <iostream>
// 基底クラス(親)
class Enemy {
public:
Enemy(int hp) : m_hp(hp) {}
virtual ~Enemy() {} // 仮想デストラクタ(後述)
void TakeDamage(int damage) {
m_hp -= damage;
if (m_hp < 0) m_hp = 0;
}
bool IsDead() const { return m_hp <= 0; }
// 派生クラスで上書きできる関数(virtual)
virtual void Attack() const {
std::cout << "敵の攻撃!\n";
}
protected:
int m_hp; // ← protected:派生クラスからアクセスできる
};
// 派生クラス①:スライム
class Slime : public Enemy {
public:
Slime() : Enemy(30) {} // 親のコンストラクタを呼ぶ
void Attack() const override { // 親の Attack() を上書き
std::cout << "スライムが体当たり!(5ダメージ)\n";
}
};
// 派生クラス②:ドラゴン
class Dragon : public Enemy {
public:
Dragon() : Enemy(500) {}
void Attack() const override {
std::cout << "ドラゴンが炎を吐く!(80ダメージ)\n";
}
// ドラゴン固有の機能
void Roar() const {
std::cout << "ドラゴンが咆哮!HPは " << m_hp << "\n";
// m_hp は protected なので派生クラスからアクセスできる
}
};▼main.cpp
#include "enemy.h"
int main() {
Slime slime;
Dragon dragon;
slime.Attack();
dragon.Attack();
dragon.Roar();
dragon.TakeDamage(100); // 親の関数もそのまま使える
std::cout << "ドラゴン死亡: " << (dragon.IsDead() ? "Yes" : "No") << "\n";
return 0;
}実行結果
スライムが体当たり!(5ダメージ)
ドラゴンが炎を吐く!(80ダメージ)
ドラゴンが咆哮!HPは 500
ドラゴン死亡: No
protectedとは?
先ほどのコードで m_hp を protected にしていました。
これが今回のポイントです。
protectedとは、
- 「クラス内部と派生クラスからはアクセスできるが、外部からはアクセスできない」
アクセス修飾子です。private と public の中間の存在です。
3つの違いを表にまとめます。
| 修飾子 | クラス内部 | 派生クラス | クラス外部 |
|---|---|---|---|
| public | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ❌ |
| private | ✅ | ❌ | ❌ |
m_hp を private にしてしまうと、Dragon::Roar() の中で m_hp を読もうとしてもコンパイルエラーになります。
「派生クラスには使わせたいが、外からは触られたくない」というメンバには protected を使いましょう。
virtual(仮想関数)と override
継承と合わせて必ず覚えておきたいのが virtual と override です。
- virtual:親クラスの関数に付けることで、派生クラスで上書き(オーバーライド)できるようになる
- override:派生クラス側に付けることで、「親の仮想関数を上書きしています」と明示できる(スペルミスなどを防ぐ安全装置)
virtual を付けない場合どうなるか、比べてみましょう。
// ❌ virtual なし(よくある失敗)
class Enemy {
public:
void Attack() const { std::cout << "敵の攻撃!\n"; }
};
class Dragon : public Enemy {
public:
void Attack() const { std::cout << "炎を吐く!\n"; }
};
int main() {
Enemy* e = new Dragon();
e->Attack(); // ← 「炎を吐く!」ではなく「敵の攻撃!」が呼ばれる!
delete e;
return 0;
}実行結果
敵の攻撃! ← Dragon の Attack が呼ばれない!
virtual がないと、ポインタの型(Enemy*)を見て関数を呼ぶため、Dragonのものが無視されます。virtual を付けると、ポインタが指している実際のオブジェクトの型(Dragon)を見て呼び分けてくれます。これをポリモーフィズム(多態性)といいます。
仮想デストラクタを忘れずに
基底クラスのデストラクタには必ず virtual を付けてください。
// ❌ 仮想デストラクタなし(メモリリークの危険)
class Enemy {
public:
~Enemy() { std::cout << "Enemy 消えた\n"; }
};
// ✅ 仮想デストラクタあり(正しい)
class Enemy {
public:
virtual ~Enemy() { std::cout << "Enemy 消えた\n"; }
};Enemy* で Dragon を delete するとき、仮想デストラクタがないと Dragon のデストラクタが呼ばれません。基底クラスのデストラクタには必ず virtual を付けると覚えておきましょう。
【重要】私が実際にゲーム制作でハマった体験談
自主制作のRPGで敵キャラを継承で設計したとき、まんまとこの問題にハマりました。
Enemy を親にして Slime と Dragon を作り、それぞれ Attack() を書いたのですが、Enemy* のポインタ配列に入れて呼び出すとどの敵も全員「敵の攻撃!」という親の処理しか実行しない状態になってしまいました。
30分以上悩んだ末に気づいたのは、親クラスの Attack() に virtual を付け忘れていたことでした。
// ❌ 付け忘れていたコード
void Attack() const { std::cout << "敵の攻撃!\n"; }
// ✅ 直したコード
virtual void Attack() const { std::cout << "敵の攻撃!\n"; }virtual の一語を足すだけで、全員がそれぞれの攻撃を正しく出すようになりました。
「派生クラスで上書きしたい関数には、必ず親側に virtual を付ける」を身に染みて覚えた出来事です。
よくある失敗例と対処法
- 親クラスに virtual を付け忘れる
→ ポインタ経由で呼ぶと、派生クラスの関数が無視されて親の関数が呼ばれ続けます。オーバーライドしたい関数には必ず親側にvirtualを付けましょう。
- override を付け忘れてスペルミスに気づかない
→Atack()のようにスペルを間違えても、overrideなしではコンパイルエラーになりません。派生クラス側には必ずoverrideを付けて、意図通りの上書きかコンパイラに確認させましょう。
- 基底クラスのデストラクタに virtual を付け忘れる
→Enemy*でdeleteしたとき、派生クラスのデストラクタが呼ばれずにメモリリークが起きます。基底クラスのデストラクタには必ずvirtual ~クラス名()と書きましょう。
発展:純粋仮想関数(抽象クラス)
仮想関数に = 0 を付けると純粋仮想関数になります。純粋仮想関数を持つクラスは抽象クラスと呼ばれ、直接インスタンスを作れません。
class Enemy {
public:
virtual ~Enemy() {}
// 純粋仮想関数:派生クラスで必ずオーバーライドしなければならない
virtual void Attack() const = 0;
protected:
int m_hp = 0;
};
// Enemy enemy; ← ❌ コンパイルエラー(抽象クラスは直接生成不可)
// Slime slime; ← ✅ Attack() を override していれば生成できる「すべての派生クラスに必ず実装させたい関数」には純粋仮想関数を使うと、実装漏れをコンパイルエラーで検出できます。
継承とデザインパターンを組み合わせた実践的な設計については、デザインパターンをゲーム開発で活用する記事やステートパターンの記事もあわせて読んでみてください。
まとめ
- ✅ 継承:既存クラス(親)の機能を引き継いで新しいクラス(子)を作る仕組み
- ✅ protected:クラス内部と派生クラスからはアクセスできるが、外部からはアクセスできない
- ✅ private との違い:
privateは派生クラスからもアクセス不可、protectedは派生クラスからアクセスできる - ✅ virtual:親の関数に付けることで派生クラスでオーバーライドできるようになる
- ✅ override:派生クラス側に付けることで、意図通りの上書きかコンパイラが確認してくれる
- ✅ 基底クラスのデストラクタには必ず
virtualを付ける - ✅ 純粋仮想関数(= 0):派生クラスに実装を強制できる
これでC++の継承とprotectedの使い方はバッチリですね!
ここまで読んでくださり、ありがとうございました。
この記事が皆様の学習の役に立てば幸いです。


