継承

継承によるコードの再利用の促進は OOP の特徴です。新しいクラスは、基本クラスと呼ばれる既存するクラスから作られます。派生クラスは基本クラスのメンバを使用するだけでなくそれらを変更したり補完したりすることが出来ます。

多くの型は既存の型のバリエーションです。多くの場合、各バリエーションのために新しいコードを開発することは面倒です。また、新しいコードは新しいエラーを意味します。派生クラスは基本クラスの記述を継承するのでコードの再開発と再テストは不要です。継承関係は階層構造を持ちます。

階層構造は、要素の全ての多様性と複雑さを複製することを可能にします。それはオブジェクトの分類を紹介します。例えば、元素表はガスを含みます。ガスは全ての元素に固有の特性を保有します。

不活性ガスは次に重要なサブクラスを構成します。ここでみられる階層は、アルゴン等の不活性ガスがガスで、ガスはひいてはシステムの一部であるということです。このような階層を使用すれば、不活性ガスの挙動が簡単に解釈出来ます。不活性ガスは他の全ての要素と同様に原子に陽子と電子があることがわかります。

不活性ガスは全てのガスのように常温で気体状態であることもわかります。不活性ガスのサブクラスに属するガスは全ての不活性ガスの特性であるように他の元素との通常の化学反応に入らないこともわかります。

幾何学的図形の継承の例を考えてみましょう。数々の単純な図形を記述するのに(円形、三角形、四角形、正方形等)一番良い方法は、全ての派生クラスの祖先である基本クラスを作成することです( ADT )。

図形を記述する最も一般的なメンバを含む基本クラス CShape を作成してみましょう。これらのメンバは図形の特徴であるプロパティ(図形の種類とメインアンカーポイントの座標)を記述します。

例:

//--- 基本クラス Shape
class CShape
 {
protected:
  int       m_type;                   // 図形の種類
  int       m_xpos;                   // 基点の X 座標
  int       m_ypos;                   // 基点の Y 座標
public:
            CShape(){m_type=0; m_xpos=0; m_ypos=0;} // コンストラクタ
  void      SetXPos(int x){m_xpos=x;} // X を設定
  void      SetYPos(int y){m_ypos=y;} // Y を設定
 };

次に、基本クラスから派生した新しいクラスを作成し、それぞれのクラスを指定するのに必要なフィールドを追加します。円形の場合には、半径値が含まれているメンバを追加する必要があります。正方形は側面値によって特徴付けられます。従って、基本クラスの CShape から継承された派生クラスは次のように宣言されます。

//--- 派生クラス circle
class CCircle : public CShape       // コロンの後に基本クラスを定義する
 {                                   // 継承の元
private:
  int             m_radius;           // 円の半径
 
public:
                  CCircle(){m_type=1;}// コンストラクタ タイプ 1
 };

正方形クラスの宣言も似ています。

//--- 派生クラス Square
class CSquare : public CShape       // コロンの後で基本クラスを定義する
 {                                   // 継承の元
private:
  int            m_square_side;       // 正方形の側面
 
public:
                 CSquare(){m_type=2;} // コンストラクタ(タイプ 2)
 };

オブジェクト作成時に基本クラスのコンストラクタが最初に呼び出されその後で派生クラスのコンストラクタが呼び出されることには留意するべきです。オブジェクト破壊時には派生クラスのデストラクタが呼び出されてから基本クラスのデストラクタ が呼び出されます。

従って、最も一般的なメンバを基本クラスの中で宣言することによって、派生クラスに特定のクラスを指定するメンバを追加することが出来ます。継承は何度も再利用することが出来る力強いコードライブラリの作成を可能にします。

既存クラスから派生クラスを作成するための構文は次の通りです。

class クラス名 :
         (public | protected | private) opt  基本クラス名
 {                                    
   クラスメンバの宣言
 };

 

派生クラスの側面の 1 つは、そのメンバの後継者の視認性(オープンネス)です。public、protected 及び private キーワードは 基本クラスのメンバが派生クラスにどの程度使用可能かを示すものです。派生クラスヘッダーのコロンの後の public キーワードは、基本クラス CShape の protected 及び public メンバが派生クラス CCircle の protected 及び public メンバとして継承される必要があることを示します。

基本クラスの private クラスメンバは、派生クラスでは使用出来ません。public 継承は、派生クラス( CCircle と CSquare )が CShapes であることを意味します。つまり、Square( CSquare )は図形( CShape )ですが、その図形は必ずしも正方形である必要はありません。

派生クラスは基本クラスのバージョン例で基本クラスの protected と public メンバを継承します。基本クラスのコンストラクタとデストラクタは継承出来ません。基本クラスのメンバに加えて、派生クラスには新しいメンバが追加されます。

派生クラスは、基本クラスとは違うメンバ関数の実装を含むことが出来ます。これは、同名の関数の意味がシグネチャの違いによって異なるものになるオーバーロードとは全く違うものです。

protected 継承では、基本クラスの public 及び protected メンバは派生クラスの protected メンバになります。private 継承では、基本クラスの public 及び protected メンバは派生クラスの private メンバになります。

protected 及び private 継承では「派生クラスのオブジェクトは基本クラスのオブジェクトである」という関係は当てはまりません。protected 及び private 継承タイプはまれであり慎重に使用される必要があります。

継承の種類( public、protected 及び private )は派生クラスが基本クラスメンバにアクセスする方法に影響を及ぼさないことは留意されるべきです。全ての継承のタイプで、public 及び protected のアクセス指定とともに宣言された基本クラスのメンバのみが派生クラスによって利用出来ます。次の例を見てみましょう。

#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link     "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| いくつかのアクセスタイプを持つクラスの例                                       |
//+------------------------------------------------------------------+
class CBaseClass
 {
private:             //--- private メンバは派生クラスから利用出来ない
  int               m_member;
protected:           //--- 基本クラスの protected メソッドは派生クラスでも利用出来る
  int               Member(){return(m_member);}
public:             //--- クラスコンストラクタは全てのクラスメンバから利用出来る
                    CBaseClass(){m_member=5;return;};
private:             //--- m_member に値を代入する private メソッド
  void              Member(int value) { m_member=value;};
 
 };
//+------------------------------------------------------------------+
//| 間違えを含む派生クラス                                                 |
//+------------------------------------------------------------------+
class CDerived: public CBaseClass // 継承は public がデフォルトであるため、public の指定は省略出来る
 {
public:
  void Func() // 派生クラスで、基本クラスメンバを呼び出す関数を定義する
    {
    //--- 基本クラスの private メンバを変更する試み
     m_member=0;       // エラー。基本クラスの private メンバは利用出来ない
     Member(0);         // エラー。基本クラスの private メソッドは派生クラスで利用出来ない
    //--- 基本クラスメンバを読む
    Print(m_member);   // エラー。基本クラスの private メンバは利用出来ない
    Print(Member());   // 成功。基本クラスの protected メソッドは派生クラスで使用出来る
    }
 };

上記の例では CBaseClass の唯一の public メソッドはコンストラクタです。コンストラクタはクラスオブジェクトを作成する時に自動的に呼び出されます。従って、private メンバ m_member と protected メソッド Member() は外から呼び出すことは出来ません。しかし public 継承では、基本クラスの Member() メソッドは派生クラスから利用出来るようになります。

protected 継承では、基本クラスの public 及び protected メンバは派生クラスの protected メンバになります。これは、もし基本クラスの public データメンバとメソッドが外からアクセス出来たとすると、protected 継承ではそれらは派生クラスまた派生クラスの派生クラスなどによってのみ利用可能なことを意味します。

//+------------------------------------------------------------------+
//| いくつかのアクセスタイプを持つクラスの例                                       |
//+------------------------------------------------------------------+
class CBaseMathClass
 {
private:             //--- private メンバは派生クラスから利用出来ない
  double            m_Pi;
public:             //--- m_Pi 値の取得と設定
  void              SetPI(double v){m_Pi=v;return;};
  double            GetPI(){return m_Pi;};
public:             // クラスコンストラクタは全てのメンバから利用出来る
                    CBaseMathClass() {SetPI(3.14); PrintFormat("%s",__FUNCTION__);};
 };
//+------------------------------------------------------------------+
//| m_Pi が変更出来ない 派生クラス                                         |
//+------------------------------------------------------------------+
class CProtectedChildClass: protected CBaseMathClass // Protected 継承
 {
private:
  double            m_radius;
public:             //--- 派生クラスの Public メソッド
  void              SetRadius(double r){m_radius=r; return;};
  double            GetCircleLength(){return GetPI()*m_radius;};
 };
//+------------------------------------------------------------------+
//| スクリプトを開始する関数                                                 |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 派生クラスの作成時に、基本クラスのコンストラクタは自動的に呼び出される
  CProtectedChildClass pt;
//--- 半径を指定する
  pt.SetRadius(10);
  PrintFormat("Length=%G",pt.GetCircleLength());
//--- SetPI() は protected であるため、以下の文字列をコメントした場合コンパイルの段階でエラーが発生する
// pt.SetPI(3);
 
//--- 基本クラスの変数を宣言し Pi 定数を 10 に設定してみる
  CBaseMathClass bc;
  bc.SetPI(10);
//---結果
  PrintFormat("bc.GetPI()=%G",bc.GetPI());
 }

この例は基本クラス CBaseMathClass の SetPI() 及び GetPi() メソッドがプログラムのどこからでも呼び出せることを示します。 しかし同時に、それらから派生する CProtectedChildClass では、これらのメソッドは CProtectedChildClass クラスまたはその派生クラスのメソッドのみからしか呼び出せません。

private 継承では基本クラスの public 及びprotected メンバは派生クラスの private メンバになり、それ以上の継承を通してそれらを呼び出すことは不可能です。

MQL5 言語は多重継承を持っていません。

 

メソッドの隠蔽

派生クラスが、基底クラスと同じ名前のメソッドを定義した場合、親クラスのメソッドは隠蔽されます(以前のMQLコンパイラのバージョンでは、派生クラスのメソッドは基底クラスの同名メソッドとオーバーロードされ、すべてのメソッドが親クラスでも利用可能でした)。

隠蔽された基底クラスのメソッドを呼び出すには、呼び出し時にスコープを明示的に指定する必要があります。

class Base
 {
public:
  void Print(int x)    { ::Print("Base int: ", x); }
  void Print(double y) { ::Print("Base double: ", y); }
 };
class Derived : public Base
 {
public:
  void Print(string s) { ::Print("Derived string: ", s); }
 };
void OnStart()
 {
  Derived d;
  d.Print("text");   // Derived::Print(string)を呼び出す
  d.Print(10);       // Base::Printは隠蔽されており、呼び出し不可
  d.Base::Print(10); // 親クラスの隠蔽メソッドを明示的に呼び出す
 }

 

usingを使ったオーバーロードの回復

using演算子は、親クラスで隠されたオーバーロードを派生クラスのスコープに戻すために使用されます。

class Base
 {
protected:
  void Print(int x)   { ::Print("Base int: ", x); }
  void Print(double y){ ::Print("Base double: ", y); }
 };
class Derived : public Base
 {
public:
  void Print(string s){ ::Print("Derived string: ", s); }
  using Base::Print; // BaseからすべてのPrintオーバーロードを戻す
 };
void OnStart()
 {
  Derived d;
  d.Print("text");   // Derived::Print(string)
  d.Print(42);       // Base::Print(int)
  d.Print(3.14);     // Base::Print(double)
 }

using Base::Print;を削除すると、d.Print(42)とd.Print(3.14)の呼び出しは利用できなくなり、Derived::Print(string)のみが残ります。

さらに、この例では、基底クラスのprotectedメソッドが派生クラス内でアクセス可能になることがわかります(protectedpublicに置き換えられます)。

 

アクセス性とスコープ

using演算子は、基底クラスのメソッドを派生クラスのスコープに戻すだけでなく、そのアクセスレベルを変更することもできます。基底クラスのprotected修飾子付きメソッドは、派生クラスのメソッド内からのみアクセス可能です。しかし、using演算子を使うことで、外部コードからもアクセス可能にすることができます。ただし、using宣言が派生クラスのpublicセクションにある場合に限ります。

class Base
 {
protected:
  void Hidden() { ::Print("Base::Hidden"); }
 };
class Derived : public Base
 {
public:
  using Base::Hidden; // メソッドがpublicになる
 };
void OnStart()
 {
  Derived d;
  d.Hidden(); // 使用可能になる
 }

 

 

参照

構造体とクラス