C++の継承は、既存のクラス(基底クラス)の性質を受け継ぎつつ、必要な機能を追加して新しいクラス(派生クラス)を作る仕組みです。
重複を避けて共通部分を一箇所にまとめられるため、学習コストに見合う大きな効果があります。
本記事では、初心者の方に向けて、文法と使いどころ、そして注意点を順序立てて解説します。
クラスの継承とは
基底クラスと派生クラスの意味
用語の整理(基底/派生/継承)
継承では、既存のクラスを基底クラス(Base)
、そこから作る新しいクラスを派生クラス(Derived)
と呼びます。
派生クラスは、基底クラスのpublic
メンバ(関数やデータ)を引き継ぎ、さらに自分だけのメンバを追加できます。
基底は共通の性質、派生は共通の性質に加えて具体化や拡張という関係です。
具体例で考える
たとえば「図形」を表すShape
を基底クラスとし、「円」を表すCircle
を派生クラスにします。
「面積を求める」という共通の振る舞いはShape
に置き、半径など円固有の情報はCircle
に追加します。
共通を上に、個別を下にが基本方針です。
継承のメリット(コード再利用)
継承の最大の利点はコード再利用です。
重複を避けることで、バグ修正や仕様変更が一箇所で済み、保守性が上がります。
同じ仕様を複数箇所でコピペせず、共通部分を基底クラスにまとめることで品質が安定します。
重複の削減と変更の一元化
- 同じ処理を複数の派生で共有できるため、修正が波及しません。
- 入出力やログといった横断的処理も基底に集約できます。
- テスト観点が整理され、影響範囲の把握が容易になります。
継承が向く場面
自然なis-a
関係(〜は〜である)が言えるときに継承は適しています。
例えばDog is a Animal
、Circle is a Shape
のような関係です。
逆に、has-a
(〜を持つ)関係は継承ではなく合成(コンポジション)が向きます。
向く例と向かない例
- 向く例: さまざまな図形を同じ「図形」として扱いたい場合
- 向かない例: 「車はエンジンを持つ」は
has-a
であり、継承よりも部品として持たせる設計が自然です
「〜は〜である」と言えるかを最初に確かめると設計ミスを防げます。
派生クラスの作り方(書き方)
class Derived : public Base の基本構文
継承はclass Derived : public Base
と書きます。
ここでのpublic
は、基底のpublic
メンバを派生でもpublic
として扱う指定です。
本記事では初心者がまず覚えるべきpublic継承に絞って解説します。
構文の要点
- 派生クラスは基底クラス名をコロンの後に書きます。
- コンストラクタでは、基底部分の初期化をコンストラクタ初期化子で呼び出します。
- 基底の
public
メンバ関数は、そのまま利用できます。
最小サンプルコードの流れ
以下は、基底Animal
から派生Dog
を作り、基底の関数を使いつつ、派生で機能を追加する最小例です。
コンストラクタとデストラクタの呼ばれる順序にも注目してください。
#include <iostream>
#include <string>
// 基底クラス(共通の性質を表す)
class Animal {
public:
// 名前を受け取るコンストラクタ
Animal(const std::string& name) : name_(name) {
std::cout << "Animal ctor: " << name_ << "\n";
}
// デストラクタ(後片付け)
~Animal() {
std::cout << "Animal dtor: " << name_ << "\n";
}
// 共通の動作(基底のメンバ関数)
void speak() const {
std::cout << name_ << " makes a sound.\n";
}
// 名前を読むためのアクセサ(読み取り専用)
const std::string& name() const {
return name_;
}
private:
// データは隠蔽する(private)
std::string name_;
};
// 派生クラス(Animalを継承して機能を追加)
class Dog : public Animal { // public継承
public:
// 派生のコンストラクタで、基底のコンストラクタを呼ぶ
Dog(const std::string& name) : Animal(name) {
std::cout << "Dog ctor: " << this->name() << "\n";
}
// 派生のデストラクタ
~Dog() {
std::cout << "Dog dtor: " << name() << "\n";
}
// 派生クラスで追加した機能
void bark() const {
// 基底のpublicメンバ関数をそのまま呼べる
speak();
std::cout << name() << " barks: Bow-wow!\n";
}
};
int main() {
Dog d("Pochi"); // まずAnimalのコンストラクタ、次にDogのコンストラクタ
d.speak(); // 基底の関数
d.bark(); // 追加した派生の関数
// ここを抜けると、まずDogのデストラクタ、次にAnimalのデストラクタ
return 0;
}
Animal ctor: Pochi
Dog ctor: Pochi
Pochi makes a sound.
Pochi makes a sound.
Pochi barks: Bow-wow!
Dog dtor: Pochi
Animal dtor: Pochi
生成時は「基底→派生」、破棄時は「派生→基底」の順で呼ばれることが確認できます。
基底メンバの使い方
基底のメンバ関数を呼ぶ
派生クラスのメンバ関数内では、基底クラスのpublic
関数をそのまま呼べます。
必要に応じてBase::関数名
と明示的に指定することも可能です。
まずは「そのまま呼べる」ことを覚え、衝突時に限定して修飾を使うと混乱が少ないです。
明示修飾の例
// 仮にDogにもspeak()があったとして、基底Animalのspeak()を呼びたい場合
// Base::speak() のように明示できます(ここでは説明のための断片コード)。
Animal::speak(); // グローバルで書く場合の例
// クラス内なら Base::speak(); とする
派生クラスで機能を追加する
派生では新しいデータや関数を追加できます。
前述のDog::bark()
のように、基底の機能を活かしながら振る舞いを豊かにします。
「共通は基底、付加価値は派生」を守ると、読みやすく保守性も高まります。
追加の小例: 情報の付与
#include <iostream>
#include <string>
class Animal {
public:
Animal(std::string name) : name_(std::move(name)) {}
void info() const { std::cout << "name=" << name_ << "\n"; }
const std::string& name() const { return name_; }
private:
std::string name_;
};
class Dog : public Animal {
public:
Dog(std::string name, int age) : Animal(std::move(name)), age_(age) {}
void infoWithAge() const {
// 基底の情報 + 追加情報を出力
info(); // Animal::info()
std::cout << "age=" << age_ << "\n";
}
private:
int age_ = 0; // 追加したデータ
};
int main() {
Dog d("Pochi", 5);
d.infoWithAge();
}
name=Pochi
age=5
アクセスできる範囲の基本(public/private)
派生クラスは基底のpublic
メンバにアクセスでき、private
メンバには直接アクセスできません。
データは基底側で隠蔽し、必要ならpublic
なアクセサを用意します。
以下は最低限のアクセス早見表です。
基底の指定 | 派生からのアクセス | クラス外(利用者)からのアクセス |
---|---|---|
public | 可能 | 可能 |
private | 不可(直接は不可) | 不可 |
迷ったら「データはprivate、操作をpublic」を基本にしましょう。
継承の注意点
is-a 関係を満たすか確認
派生は常に基底として扱えて自然であるべきです。
設計段階で「この派生は本当にその基底の一種と言えるか」を言葉で説明してみて、違和感があれば継承を再検討します。
典型的な違和感のシグナル
- 基底の契約(前提条件/後続条件)を派生が弱められない
- 派生で利用者の予想が大きく外れる動きをしない
無理な継承は避ける
実装の都合だけで継承を選ぶと、後で破綻します。
見た目の再利用に引っ張られず、意味が通るかを優先します。
「〜を持つ」関係は合成で表すことを検討してください。
補足(一歩先の話)
多態的に扱う設計ではvirtual
関数やvirtual
デストラクタが必要になりますが、詳細は別記事で扱います。
ここではまずpublic継承の型関係と再利用の感覚をつかむことが先決です。
まとめ
継承は共通部分を基底にまとめ、派生で拡張するための仕組みで、C++初心者がオブジェクト指向を学ぶうえで重要な柱です。
まずはclass Derived : public Base
という基本構文と、基底のpublic
メンバをそのまま使えることを押さえ、「is-a」が自然に成り立つ場面でのみ使うようにしましょう。
小さなサンプルを動かし、コンストラクタ/デストラクタの呼び順や基底メンバ呼び出しの感覚を体で覚えることが、次のステップ(ポリモーフィズムなど)への近道です。