閉じる

【C++】継承のアクセス指定子まとめ|public・protected・privateの違い

C++における継承は、既存のクラスの機能を再利用し、新しいクラスを定義するための強力な仕組みです。

しかし、単に機能を継承するだけでなく、その機能を「どの範囲まで公開するか」を制御する継承のアクセス指定子を正しく理解することは、堅牢なクラス設計において極めて重要です。

本記事では、publicprotectedprivateという3種類の継承方法が、派生クラスやその外部からのアクセス権にどのような影響を与えるのかを、図解とコード例を交えて詳しく解説します。

継承におけるアクセス指定子の役割

C++の継承では、基本クラス(親クラス)のメンバが派生クラス(子クラス)に引き継がれます。

この際、派生クラスの宣言時に指定するアクセス指定子は、「基本クラスのメンバを、派生クラス内でどの程度の公開レベルとして扱うか」を決定するフィルターのような役割を果たします。

メンバ自身のアクセス権と継承の指定子の違い

まず混同しやすいのが、クラス内部のメンバに付けるアクセス指定子と、継承時に付けるアクセス指定子の違いです。

  • メンバのアクセス指定子:そのクラスのメンバが、外部や派生クラスから「直接見えるかどうか」を決めます。
  • 継承のアクセス指定子:継承されたメンバが、派生クラスにおいて「どのようなアクセス属性に格下げ(制限)されるか」を決めます。

この2つの組み合わせによって、最終的なアクセス可否が決定されます。

アクセス権の変化まとめ(早見表)

継承の種類によって、基本クラスの各メンバが派生クラスでどのような扱いになるかを以下の表にまとめました。

基本クラスでの指定public継承での扱いprotected継承での扱いprivate継承での扱い
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateアクセス不可アクセス不可アクセス不可

この表から分かる通り、基本クラスのprivateメンバは、どのような継承方法を選んでも派生クラスからアクセスすることはできません。

また、継承の指定子は、基本クラスの公開レベルを「それより厳しいレベル」へ変更することはあっても、緩めることはありません。

public継承:最も一般的な継承

public継承は、オブジェクト指向における「is-a関係(派生クラスは基本クラスの一種である)」を表現するために最も頻繁に使われます。

基本クラスの公開インターフェースがそのまま派生クラスの公開インターフェースとして維持されます。

public継承の動作と特徴

public継承では、基本クラスの公開範囲がほぼそのまま維持されます。

  • 基本クラスのpublicメンバは、派生クラスでもpublicになります。
  • 基本クラスのprotectedメンバは、派生クラスでもprotectedになります。
  • 外部からは、派生クラスを通じて基本クラスのpublicメンバにアクセス可能です。

サンプルコード:public継承

C++
#include <iostream>
#include <string>

// 基本クラス
class Animal {
public:
    std::string name;
    void eat() {
        std::cout << name << " is eating." << std::endl;
    }
protected:
    void sleep() {
        std::cout << name << " is sleeping." << std::endl;
    }
private:
    int age; // 派生クラスからもアクセス不可
};

// public継承
class Dog : public Animal {
public:
    void bark() {
        std::cout << "Woof! My name is " << name << "." << std::endl;
        // protectedメンバにはアクセス可能
        sleep(); 
    }
    // age にはアクセスできない(コンパイルエラー)
};

int main() {
    Dog myDog;
    myDog.name = "Pochi"; // OK: public継承なので外部からアクセス可能
    myDog.eat();         // OK: public継承なので外部からアクセス可能
    myDog.bark();        // OK: Dogクラスの自作メソッド
    
    // myDog.sleep();    // NG: protectedなので外部からはアクセス不可
    
    return 0;
}
実行結果
Pochi is eating.
Woof! My name is Pochi.
Pochi is sleeping.

この例では、DogAnimalpublicメンバをそのまま保持しているため、main関数から直接nameeat()を呼び出すことができます。

protected継承:継承関係内でのみ公開

protected継承は、基本クラスの機能を「そのクラスの系譜(さらに下の派生クラス)」には伝えたいが、外部(インスタンスを利用する側)には一切見せたくない場合に使用します。

protected継承の動作と特徴

protected継承を行うと、アクセスの制限が一段階厳しくなります。

  • 基本クラスのpublicメンバは、派生クラス内ではprotectedメンバとして扱われます。
  • 基本クラスのprotectedメンバは、派生クラスでもそのままprotectedです。
  • 外部からは、基本クラスのどのメンバにもアクセスできなくなります。

これは、基本クラスのインターフェースを隠蔽し、派生クラスで新しいインターフェースを再定義したい時に有効です。

サンプルコード:protected継承

C++
#include <iostream>

class Base {
public:
    void publicMethod() {
        std::cout << "Base public method" << std::endl;
    }
protected:
    void protectedMethod() {
        std::cout << "Base protected method" << std::endl;
    }
};

// protected継承
class ProtectedDerived : protected Base {
public:
    void accessBase() {
        publicMethod();    // OK: クラス内部からはアクセス可能
        protectedMethod(); // OK: クラス内部からはアクセス可能
    }
};

// さらに下の派生クラス
class GrandChild : public ProtectedDerived {
public:
    void test() {
        publicMethod();    // OK: ProtectedDerivedでprotectedになっているため継承先でも可
    }
};

int main() {
    ProtectedDerived d;
    d.accessBase();
    
    // d.publicMethod(); // NG: protected継承により外部からはアクセス不可
    
    return 0;
}
実行結果
Base public method
Base protected method

ProtectedDerivedクラスの内部では、Baseの公開メンバを自由に利用できますが、main関数からd.publicMethod()を呼ぼうとすると、アクセス権限エラーが発生します。

private継承:実装の隠蔽

private継承は、基本クラスの機能を「部品」として利用するが、その事実を外部やさらなる派生クラスから完全に隠したい場合に使用されます。

これは「has-a関係(あるいは implemented-in-terms-of 関係)」を表現する一つの手法です。

private継承の動作と特徴

private継承は、最も制限が厳しい継承です。

  • 基本クラスのpublicおよびprotectedメンバは、すべて派生クラス内でprivateメンバとなります。
  • 派生クラスのそのまた派生クラス(孫クラス)からは、基本クラスのメンバに一切アクセスできません。

サンプルコード:private継承

C++
#include <iostream>

class Engine {
public:
    void start() {
        std::cout << "Engine started." << std::endl;
    }
};

// private継承
class Car : private Engine {
public:
    void drive() {
        start(); // OK: 内部ではEngineの機能を使える
        std::cout << "Car is moving." << std::endl;
    }
};

class SportsCar : public Car {
public:
    void fastDrive() {
        drive(); // OK
        // start(); // NG: Carでprivateになっているため、孫クラスからは見えない
    }
};

int main() {
    Car myCar;
    myCar.drive();
    
    // myCar.start(); // NG: private継承なので外部からはアクセス不可
    
    return 0;
}
実行結果
Engine started.
Car is moving.

private継承を使うことで、Carは内部的にEngineの機能を利用していますが、Carの利用者はEngineの存在(メソッド)を意識する必要がなくなります。

デフォルトの継承アクセス指定子

C++では、継承時にアクセス指定子を省略することができますが、その挙動は定義する型がclassstructかによって異なります。

classとstructの違い

  • classで定義した場合:デフォルトはprivate継承になります。
  • structで定義した場合:デフォルトはpublic継承になります。
C++
class Base {};
class Derived : Base {}; // private継承扱い

struct SBase {};
struct SDerived : SBase {}; // public継承扱い

意図しないアクセス制限を防ぐためにも、継承時は常にアクセス指定子を明示的に記述することが推奨されます。

継承アクセス指定子の使い分けの指針

どの継承方法を選ぶべきかは、そのクラスがどのような目的で作られるかによります。

1. public継承を使うべきケース

最も一般的です。

基本クラスと派生クラスの間に明確なis-a関係がある場合に使用します。

  • 例:円 (Circle)図形 (Shape) である。
  • 多態性(ポリモーフィズム)を利用して、基本クラスのポインタから派生クラスを扱いたい場合に必須です。

2. private継承を使うべきケース

基本クラスの機能を「再利用」したいが、基本クラスのインターフェースを公開したくない場合に使用します。

  • 「包含(コンポジション)」に似ていますが、基本クラスのprotectedメンバにアクセスする必要がある場合や、仮想関数をオーバーライドする必要がある場合にprivate継承が選ばれます。

3. protected継承を使うべきケース

実務で使われる機会は非常に稀です。

  • 「自分の子孫クラスにはこの機能を使わせたいが、外部の利用者には隠したい」という非常に特殊な階層構造を作る場合に限定されます。

using宣言によるアクセス権の個別調整

継承の種類によって制限されたアクセス権を、特定のメンバに対してだけ個別に元の公開レベルに戻したい場合があります。

その際はusing宣言を使用します。

サンプルコード:usingによる調整

C++
#include <iostream>

class Base {
public:
    void openFunc() { std::cout << "Open" << std::endl; }
    void secretFunc() { std::cout << "Secret" << std::endl; }
};

class Derived : private Base {
public:
    // private継承したけど、openFuncだけは外部に見せたい
    using Base::openFunc; 
};

int main() {
    Derived d;
    d.openFunc();   // OK: using宣言によりpublicとして公開されている
    // d.secretFunc(); // NG: 隠蔽されたまま
    return 0;
}

このようにusingを活用することで、「基本クラスの機能の大部分は隠すが、特定の機能だけをセレクトして公開する」という柔軟な設計が可能になります。

まとめ

C++における継承のアクセス指定子は、クラス間の結合度とカプセル化を制御するための重要なツールです。

  • public継承は、外部に全ての公開機能を維持し、is-a関係を構築します。
  • protected継承は、継承の家系内でのみ機能を共有し、外部には隠蔽します。
  • private継承は、基本クラスを内部的な実装手段として利用し、外部や孫クラスからは存在を隠します。

これらを適切に使い分けることで、意図しないメンバへのアクセスを防ぎ、メンテナンス性の高いコードを実現できます。

特に「基本クラスのprivateメンバは、どのように継承しても派生クラスからは触れない」という原則と、classstructでのデフォルト挙動の違いは、バグを防ぐためにも必ず覚えておきましょう。

まずは最も標準的なpublic継承からマスターし、設計の必要性に応じて他の継承方法を検討することをお勧めします。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!