言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

C++辞典

  1. トップページ
  2. C++辞典
  3. 継承(public / protected / private)

継承(public / protected / private)

『C++』の継承は、既存のクラス(基底クラス)の機能を別のクラス(派生クラス)が引き継ぐ仕組みです。共通処理を基底クラスにまとめ、派生クラスで特有の処理を追加・上書きすることでコードの再利用性を高められます。アクセス指定子(public / protected / private)によって、継承後のメンバのアクセス範囲が変わります。

構文

// ========================================
// C++ 継承の基本構文
// ========================================

// 基底クラスの定義
class Base {
public:
    Base(int value) : value_(value) {}   // 基底クラスのコンストラクタです
    void show() { /* ... */ }            // 派生クラスでオーバーライドできます
protected:
    int value_;                          // 派生クラスからアクセス可能なメンバです
private:
    int secret_;                         // 派生クラスからもアクセス不可です
};

// 派生クラスの定義(public 継承)
class Derived : public Base {
public:
    // 基底クラスのコンストラクタを初期化子リストで呼び出します
    Derived(int value, std::string name)
        : Base(value), name_(name) {}

    // 基底クラスの show() をオーバーライドします
    void show() { /* 独自処理 */ }

private:
    std::string name_;
};

// protected 継承(基底の public メンバが protected になります)
class DerivedProt : protected Base { /* ... */ };

// private 継承(基底の public / protected メンバが private になります)
class DerivedPriv : private Base { /* ... */ };

構文一覧

構文概要
class Derived : public BaseBase を public 継承した Derived クラスを定義します。基底の public メンバは派生クラスでも public のままです。
class Derived : protected BaseBase を protected 継承します。基底の public メンバが派生クラスでは protected になります。
class Derived : private BaseBase を private 継承します。基底の public / protected メンバがすべて private になります。
Derived(args) : Base(args)派生クラスのコンストラクタ初期化子リストで基底クラスのコンストラクタを呼び出します。
void show() { /* override */ }基底クラスと同名の関数を派生クラスで再定義してオーバーライドします(仮想関数でなくても隠蔽できます)。
Base::show()派生クラス内から基底クラスの関数を明示的に呼び出します。C++ では super キーワードは存在しないため、クラス名を直接指定します。

サンプルコード

lab_member.cpp
// ========================================
// lab_member.cpp — 継承のサンプルです
// 『Steins;Gate』のキャラクターを
// 基底クラス LabMember と派生クラスで表現します
// ========================================

#include <iostream>
#include <string>

// ========================================
// 基底クラス: LabMember(未来ガジェット研究所員)
// ========================================
class LabMember {
public:
    // コンストラクタで名前とラボメン番号を受け取ります
    LabMember(std::string name, int labNo)
        : name_(name), labNo_(labNo) {}

    // メンバ情報を表示します(派生クラスでオーバーライドできます)
    void introduce() {
        std::cout << "ラボメンNO." << labNo_ << ": " << name_ << std::endl;
    }

    // ラボメン番号を取得します
    int getLabNo() const { return labNo_; }

    // 名前を取得します
    std::string getName() const { return name_; }

protected:
    std::string name_;   // 派生クラスから参照できます
    int         labNo_;  // 派生クラスから参照できます
};

// ========================================
// 派生クラス: Scientist(科学者)
// 基底クラス LabMember を public 継承します
// ========================================
class Scientist : public LabMember {
public:
    Scientist(std::string name, int labNo, std::string field)
        : LabMember(name, labNo), field_(field) {}  // 基底クラスのコンストラクタを呼び出します

    // introduce() をオーバーライドして専門分野を追記します
    void introduce() {
        LabMember::introduce();  // 基底クラスの処理をまず呼び出します(super 相当)
        std::cout << "  専門: " << field_ << std::endl;
    }

private:
    std::string field_;
};

// ========================================
// 派生クラス: Hacker(ハッカー)
// 基底クラス LabMember を public 継承します
// ========================================
class Hacker : public LabMember {
public:
    Hacker(std::string name, int labNo, std::string handle)
        : LabMember(name, labNo), handle_(handle) {}

    // introduce() をオーバーライドしてハンドル名を追記します
    void introduce() {
        LabMember::introduce();
        std::cout << "  ハンドル: " << handle_ << std::endl;
    }

private:
    std::string handle_;
};

// ========================================
// main 関数: 各クラスのインスタンスを生成して紹介します
// ========================================
int main() {
    // Scientist インスタンスを生成します(岡部倫太郎)
    Scientist okabe("岡部倫太郎", 1, "応用物理学");

    // Hacker インスタンスを生成します(橋田至)
    Hacker daru("橋田至", 3, "Super Hacka―");

    // introduce() は virtual 宣言されていないため、基底クラス型のポインタで呼ぶと
    // 常に基底クラスの LabMember::introduce() が実行されます(静的ディスパッチ)
    LabMember* members[3];
    members[0] = &okabe;
    members[1] = &daru;

    // 追加メンバ: LabMember をそのまま使います(牧瀬紅莉栖)
    LabMember kurisu("牧瀬紅莉栖", 4);
    members[2] = &kurisu;

    std::cout << "=== 未来ガジェット研究所 メンバー紹介 ===" << std::endl;
    for (int i = 0; i < 3; i++) {
        members[i]->introduce();
    }

    return 0;
}
# g++ でコンパイルします(-std=c++17 で C++17 規格を指定)
g++ -std=c++17 lab_member.cpp -o lab_member

# コンパイルが成功したら実行します
./lab_member
=== 未来ガジェット研究所 メンバー紹介 ===
ラボメンNO.1: 岡部倫太郎
  専門: 応用物理学
ラボメンNO.3: 橋田至
  ハンドル: Super Hacka―
ラボメンNO.4: 牧瀬紅莉栖

よくあるミス: virtual なしで基底クラスポインタから派生クラスのメソッドを呼ぼうとする

基底クラスのポインタから派生クラスのオーバーライドメソッドを呼ぶには、基底クラス側のメソッドに virtual を付ける必要があります。virtual がない場合は静的ディスパッチとなり、ポインタの型(基底クラス)のメソッドが呼ばれます。

// NG: virtual がないため基底クラスの introduce() が呼ばれます
class LabMember {
public:
    void introduce() { std::cout << "LabMember" << std::endl; }
};
class Scientist : public LabMember {
public:
    void introduce() { std::cout << "Scientist" << std::endl; }
};

LabMember* p = new Scientist("岡部倫太郎", 1, "物理学");
p->introduce();  // "LabMember" が表示されます(Scientist の introduce() ではありません)

OK: 基底クラス側のメソッドに virtual を付けるとポリモーフィズムが機能します。

// OK:
class LabMember {
public:
    virtual void introduce() { std::cout << "LabMember" << std::endl; }
};
class Scientist : public LabMember {
public:
    void introduce() override { std::cout << "Scientist" << std::endl; }
};

LabMember* p = new Scientist("岡部倫太郎", 1, "物理学");
p->introduce();  // "Scientist" が表示されます

よくあるミス: 基底クラスのデストラクタを virtual にし忘れる

基底クラス型のポインタで派生クラスのオブジェクトを管理するとき、基底クラスのデストラクタが virtual でないと派生クラスのデストラクタが呼ばれず、リソースリークが発生することがあります。

// NG: 基底クラスのデストラクタが virtual でありません
class LabMember {
public:
    ~LabMember() { /* 基底のクリーンアップ */ }
};

LabMember* p = new Scientist("岡部倫太郎", 1, "物理学");
delete p;  // Scientist のデストラクタが呼ばれません

OK: 基底クラスのデストラクタを virtual にします。

// OK:
class LabMember {
public:
    virtual ~LabMember() {}
};

概要

C++ の継承はクラス宣言の際に : public Base(または protected / private)と記述して行います。public 継承は "is-a" 関係を表し、基底クラスのすべての public メンバが派生クラスでも public として利用できます。protected 継承は基底の public メンバが派生クラス内では protected 扱いになり、外部からはアクセスできません。private 継承は基底のすべての公開メンバが private になり、さらにその先のクラスへは引き継がれません。派生クラスのコンストラクタは初期化子リスト(: Base(args))で基底クラスのコンストラクタを明示的に呼び出す必要があります。呼び出しを省略した場合はデフォルトコンストラクタが自動で呼ばれます。基底クラスの関数をオーバーライドするには同じ名前・シグネチャの関数を派生クラスで定義します。C++ には Java の super に相当するキーワードはありませんが、LabMember::introduce() のようにクラス名を修飾して基底クラスの実装を呼び出せます。基底クラスのポインタや参照で派生クラスを扱う多態性(ポリモーフィズム)を活用する際は、virtual キーワードを付けた仮想関数と組み合わせるのが一般的です。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。