軽傷で済んだので継承していく

こんにちは、ぷないしんです。

 

昨日の記事を継承し、続きの話に入っていこうと思います。

 

m0can.hatenablog.com

 

昨日の記事の最後に

プログラム上のあちらこちらに”似て非なる”クラスが大量に存在し、開発効率、保守性が下がります。

と書きましたが、これらの問題を解決する手段として”派生”を使いましょう。

派生とは、既存クラスの資産を継承するクラスを作り出すことです。

(派生の時は、データメンバ、メンバ関数などの資産を単純に継承するだけではなく、追加したり上書きしたりできる)

 

定義の仕方はこんな感じで

class base {
    int a;
    int b;
public:
    void func(){ /*省略*/}
};

↓派生

class Derived : base {
    int x;
public:
    void method() {/*省略*/}
}

クラスBaseと、それを継承するDerivedを定義しています。

クラスDerivedを定義するときにクラス名のDerivedの後ろに【:】、そのあとに継承元のbase

これでbaseから派生したクラスDerivedが出来ました。

 

呼び方としては

派生元

・基底クラス、上位クラス、親クラス、スーパークラス

派生先

・派生クラス、下位クラス、、子クラス、サブクラス

等があるみたいで、C++では基底クラス、派生クラスと呼ぶことが多いみたいですね。

 

さて、この二つのクラスが持つ資産を概略を表してみます。

f:id:punainen:20210326163158p:plain

この図のように、

・基底クラス base

【a】と【b】の二つの変数があり、関数は≪func≫の一つです。

・派生クラスDerived

定義を行っているときは【x】と≪method≫だけが宣言、定義されています。

ですが、baseを継承しているのでそれら二つを合わせ変数は3個、関数は2個になります。

 

派生クラスは基底クラスの資産継承すると同時に、それを部分として含むクラスの事である。

(ちなみにコンパイラによって自動的に定義されるデフォルトコンストラクタとデフォルトデストラクタ、代入演算子なども、各クラスの資源として含まれる、またフレンド関係は継承されることはない)

クラス階層図

派生クラスは基底クラスの”子供”のようなもので、その親子関係を表してみます。

f:id:punainen:20210326164435p:plain

クラス階層図

派生クラスDerivedの定義":base"の部分は

「私の親はクラスbaseです」

という宣言です。

つまり、親クラス"base"の知らないところで子供が生まれています。

 

子供は親を知っているのですが、親は子供を知りません。

親として、子供がいるのか、いないのか、もしいるのなら何人いるのかといった情報を親は持ちえません。

基底クラスの方で『○○クラスを私の子供にします』といった宣言はできないのです。

なので、矢印の向きは派生クラス→基底クラスになります。

 

ところで、派生は一度だけではなく、何度でも出来ます。

class A{
    //省略
};

class B : A{
    //省略
};

class C : B{
    //省略
};

class D : B{
    //省略
};

 

f:id:punainen:20210326170038p:plain

クラス階層図

クラスAからクラスBが派生し、クラスBからクラスC、クラスDが派生しています。

クラスBはクラスAので、クラスC、DはクラスBのです。

つまり、クラスC、DはクラスAのですね。

一部では直接の親にあたるクラスを直接基底クラスと呼び、直接の親ではないが、先祖になるクラスを間接基底クラスとよぶこともあるみたいです。

クラスD視点でみると、クラスBは直接基底クラスで、クラスAは間接基底クラスになるといった具合です。

また、「クラスDはクラスAから間接派生している/クラスBから直接派生している」といった呼ばれかたをすることもあります。

 

派生の形態

外部に公開したほうがいいデータや手続きのみを公開し、そうでないものを非公開とするのがクラス設計時の原則です。

派生クラスは基底クラスの資産を継承するが、それらをクラス外部に公開するかどうかは別問題です。

基底クラスと派生クラス内のメンバのアクセス関係はこの三種類の派生形態によって変わります。

・private派生

・protected派生

・public派生

これら指定の仕方は派生クラスを定義するときに、基底クラス名の前にアクセス指定子を書いて行います。

例えばこの様に書いた場合はpublic派生です。

class Derived : public Base {}:

また、アクセス指定子を省略したときは、自動的に【private派生】になります。

(派生クラスを定義する時のキーワードがstructの時は【public派生】になる)

 

三つの派生形態の例としてですが

#ifndef ___Super
#define ___Super

class Super{
private:
    int pri;
protected:
    int pro;
public:
    int pub;
};

#endif
private派生

クラスSuperからprivate派生を行う例です

Private.cpp

#include "Super.h"

class Sub : private Super {
    void f(){
//        pri = 1;
        pro = 2;
        pub = 3;
    }
};

int main()
{
    Sub x;

//    x.pri = 5;
//    x.pro = 6;
//    x.pub = 7;
}

 (コンパイルエラーになるところはコメントアウト済み)

実行しても何も起きないプログラムです。

private派生を行うと、派生クラスSubにとってクラスSuperは非公開の基底クラスになります。

アクセス性は以下の通りです。

f:id:punainen:20210326180530p:plain

private派生時のアクセス性

派生クラスの内部からは基底クラスの【privateメンバ】はアクセス出来ません。

また、基底クラスの【protectedメンバ】と【publicメンバ】は派生クラス内では【privateメンバ】として扱われ、派生クラスの利用者に公開されません。

 

コメントアウトした部分がコンパイルエラーになり理由ですが

①派生クラスsubの内部において、基底クラスSuperの【privateメンバ】priのアクセスは出来ない。

②基底クラスsuperの全メンバは、派生クラスsubの利用者に対して非公開である。

限定公開(protected)メンバは外部に対して存在を隠すが、直接派生クラスに対しては存在を隠さない

という事です。

protected派

クラスSuperからprotected派生を行う例です。

protected.cpp

#include "Super.h"

class Sub : protected Super {
    void f(){
//        pri = 1;
        pro = 2;
        pub = 3;
    }
};

int main()
{
    Sub x;

//    x.pri = 5;
//    x.pro = 6;
//    x.pub = 7;
}

 (コンパイルエラーになるところはコメントアウト済み)

アクセス性は以下の通りです。

f:id:punainen:20210326181859p:plain

protected派生時のアクセス性

派生クラスのメンバ関数から基底クラスの【privateメンバ】をアクセス出来ないのはprivate派生と同じです。

ただ、基底クラスの【protectedメンバ】と【publicメンバ】が派生クラス内で【protectedメンバ】として扱われる点がprivate派生と違う所です。

(これらのメンバは派生クラスの利用者に対しては非公開)

protectedメンバはSubから派生したクラスの内部ではアクセス出来ますが、その外部からは不可能です。

public派生

クラスSuperからpublic派生を行う例です。

public.cpp

#include "Super.h"

class Sub : public Super {
    void f(){
//        pri = 1;
        pro = 2;
        pub = 3;
    }
};

int main()
{
    Sub x;

//    x.pri = 5;
//    x.pro = 6;
      x.pub = 7;
}

 (コンパイルエラーになるところはコメントアウト済)

アクセス性はこちらです

f:id:punainen:20210326185255p:plain

public派生時のアクセス性

他の派生と同様に、派生クラスのメンバ関数から基底クラスの【privateメンバ】をアクセスするのは出来ません。

基底クラスの【protectedメンバ】は派生クラス中でも【protectedメンバ】としての扱いで、基底クラスの【publicメンバ】は派生クラスでも【publicメンバ】として扱われるので、派生クラスの利用者に公開されます。

つまり、基底クラスのprivate以外のメンバ(protected、public)のアクセス性が派生クラスでも維持されています。

派生のカタチ

三種類の派生(private、protected、public)に共通する原理や規則性を纏めると

どの派生でも基底クラスの非公開(private)メンバは派生クラスからはアクセスできない

”〇〇派生”を行うと、基底クラスの公開(public)メンバが派生クラスの”○○部”に所属するようになる

限定公開(protected)メンバは外部には公開されないが自分の子(直接派生するクラス)には公開される

 

こんなところでしょうか。

以上がクラスの継承の話でした。

協力者のキムラ、クサナギ(敬称略)に感謝をしつつ本日はこのあたりにしておきます。

 

ありがとうございました。

 

ぷないしん