🌊

[C++] public,protected,private継承の仕様

2023/07/22に公開

C++は基底クラスを継承する際に以下のようにpublic等のキーワードを付与することができる。

class Deliver : public Base

今まで何も考えずにとりあえずpublicのキーワードを付与していたが、しっかり理解すべく今回は仕様を調査してみた。

派生クラスのインスタンスで使えるメソッドが変わる

以下は基底クラスをそれぞれpublic,protected,privateで継承した際の動作の違いを検証した結果だ。publicで基底を継承したクラスをインスタンス化した場合、基底クラスのpublicメソッドは呼び出して使えるが、protectedやprivateでは基底クラスのpublicメソッドを使えなくなる。

class Base
{
public:
    void BasePublicFunc() {}
protected:
    void BaseProtectedFunc() {}
private:
    void BasePrivateFunc() {}
};

class DeliverUsePublic : public Base
{
};

class DeliverUseProtected : protected Base
{
};

class DeliverUsePrivate : private Base
{
};

int main()
{
    // 基底クラスをpublic継承
    DeliverUsePublic deliverUsePublic;
    deliverUsePublic.BasePublicFunc();    // OK
//  deliverUsePublic.BaseProtectedFunc(); // NG
//  deliverUsePublic.BasePrivateFunc();   // NG

    // 基底クラスをprotected継承
    DeliverUseProtected deliverUseProtected;
//  deliverUseProtected.BasePublicFunc();    // NG
//  deliverUseProtected.BaseProtectedFunc(); // NG
//  deliverUseProtected.BasePrivateFunc();   // NG

    // 基底クラスをprivate継承
    DeliverUsePrivate deliverUsePrivate;
//  deliverUsePrivate.BasePublicFunc();    // NG
//  deliverUsePrivate.BaseProtectedFunc(); // NG
//  deliverUsePrivate.BasePrivateFunc();   // NG

    return 0;
}

これは以下のような仕様になっているのが理由とのこと。

  • public継承の場合: 基底のアクセス指定子はそのまま
  • protected継承の場合: 基底のpublicのアクセス指定子はprotectedへ変わる
  • private継承の場合: 基底のpublic,protectedのアクセス指定子はprivateへ変わる

ちなみに以下のようにキーワードを書かずに継承する場合、暗黙的にprivate継承になる。

class Deliver : Base

再度派生させたクラスで使えるメソッドが変わる

ここまでの内容を見るとprotected継承とprivate継承は動作の違いは無いように見えるが、動作で違いが以下のようなケースで動作に違いがでる。

以下はprotected継承したクラスとprivate継承したクラスを再度派生させたクラスを使ったコードだが、再度派生したクラスで使えるメソッドに差がでる。
protected継承した場合はBaseのprotectedメソッドも使えるが、private継承した場合は使えない。

class Base
{
public:
    void BasePublicFunc() {}
protected:
    void BaseProtectedFunc() {}
private:
    void BasePrivateFunc() {}
};

class DeliverUseProtected : protected Base
{
};

class DeliverUsePrivate : private Base
{
};

class DeliverUseProtected2 : public DeliverUseProtected
{
public:
    void func()
    {
        BasePublicFunc();    // OK
        BaseProtectedFunc(); // OK
//      BasePrivateFunc();   // NG
    }
};

class DeliverUsePrivate2 : public DeliverUsePrivate
{
public:
    void func()
    {
//        BasePublicFunc();    // NG
//        BaseProtectedFunc(); // NG
//        BasePrivateFunc();   // NG
    }
};

public継承していないクラスは基底のポインタへキャストできない

public継承していない場合、基底クラスのpublicメソッドが使えなくなるのでis-a関係が成立しなくなる。そのため、以下のように基底クラスへのポインタへキャストができなくなる。

    Base* pBase1 = &deliverUsePublic;    // OK
//  Base* pBase2 = &deliverUseProtected; // NG
//  Base* pBase3 = &deliverUsePrivate;   // NG

このように基底クラスへキャストができないとポリモーフィズムが上手く使えない。
そのため、protectedやprivate継承は個人的にはあまり使うべきではないと思う。

基底クラス継承というのはis-a関係が成立していない場合は使うべきではないが、has-a関係の場合でも継承を使って実装できるようにprotectedやprivate継承がC++で用意されている機能なのかもしれない。

Discussion