ポリモーフィズム
ポリモーフィズムは継承によって繋がったオブジェクトクラスが同じ関数要素を呼び出す時に使用されます。これは基本クラスだけでなくその子孫クラスの振る舞いも記述する普遍的なメカニズムを作成するのに役立ちます。
図形の面積を計算するために設計された基本クラス CShape を開発しメンバ関数 GetArea() を定義していきましょう。基本クラスからの継承によって生成された子孫クラスの全てで、特定の図形の面積を計算する規則に従って、この関数を定義し直します。
正方形( CSquare クラス)では面積がその辺を介して計算され、円( CCircle クラス)では面積が半径で表現されます。基本クラスと全ての子孫クラスの CShape 型のオブジェクトを格納出来る配列を作成することが出来ます。 更に配列の各要素に同じ関数を呼び出すことが出来ます。
例:
//--- 基本クラス class CShape { protected: int m_type; // 図形の種類 int m_xpos; // 基点の X 座標 int m_ypos; // 基点の Y 座標 public: void CShape(){m_type=0;}; // コンストラクタ(タイプ 0 ) int GetType(){return(m_type);};// 図形の種類を返す virtual double GetArea(){return (0); }// 図形の面積を返す }; |
これで、派生クラスの全てがゼロの値を返すメンバ関数 getArea() を持つことになります。この関数の実装は各子孫関数内で異なります。
//--- 派生クラス circle class CCircle : public CShape // コロンの後に基本クラスを定義する { // 継承の元 private: double m_radius; // 円の半径 public: void CCircle(){m_type=1;}; // コンストラクタ(タイプ 1 ) void SetRadius(double r){m_radius=r;}; virtual double GetArea(){return (3.14*m_radius*m_radius);}// 円の面積 }; |
Square クラスの宣言も同じです。
//--- 派生クラス Square class CSquare : public CShape // コロンの後に基本クラスを定義する { // 継承の元 private: double m_square_side; // 正方形の側面 public: void CSquare(){m_type=2;}; // コンストラクタ(タイプ 1 ) void SetSide(double s){m_square_side=s;}; virtual double GetArea(){return (m_square_side*m_square_side);}// 正方形の面積 }; |
正方形と円の面積を計算するためにはそれぞれ m_radius と m_square_side の値が必要となるので、それぞれのクラス宣言に関数 SetRadius() と SetSide() を追加します。
1 つの基本型 CShape から派生した異なる種類のオブジェクト( CCircle と CSquare )が両方 1 つのプログラムで使用されていると仮定します。ポリモーフィズムによって、基本クラス CShape オブジェクトの配列が作成出来ますが、配列の宣言時にはこれらのオブジェクトはまだ未知で型も定義されていません。
配列の各要素に含まれるオブジェクト型はプログラムの実行中に直接決定されます。これは関係するクラスオブジェクトの動的作成を必要とするのでオブジェクトのかわりにオブジェクトポインタを使用することが必要となります。
new 演算子はオブジェクトの動的作成に使用されます。このようなオブジェクトは、個別に明示的に delete 演算子を使用して削除される必要があります。そこで、CShape 型のポインタの配列を宣言し、次のスクリプトの例に示すように各要素に適切な型のオブジェクトを作成します( new クラス名 )。
//+------------------------------------------------------------------+ //| スクリプトプログラムを開始する関数 | //+------------------------------------------------------------------+ void OnStart() { //--- 基本型のオブジェクトポインタの配列を宣言する CShape *shapes[5]; // CShape オブジェクトポインタの配列 //--- 配列に派生オブジェクトを書き込む //--- CCircle 型のオブジェクトポインタを宣言する CCircle *circle=new CCircle(); //--- circle ポインタでオブジェクトのプロパティを設定する circle.SetRadius(2.5); //--- shapes[0] にポインタ値を入れる shapes[0]=circle; //--- CCircle オブジェクトをあと 1 つ作成し shapes[1] にそのポインタを書き込む circle=new CCircle(); shapes[1]=circle; circle.SetRadius(5); //--- 意図的に shapes[2] の値を設定するのを忘れる //circle=new CCircle(); //circle.SetRadius(10); //shapes[2]=circle; //--- 使用されない要素は NULL に設定する shapes[2]=NULL; //--- CSquare オブジェクトを作成し shapes[3] にそのポインタを書き込む CSquare *square=new CSquare(); square.SetSide(5); shapes[3]=square; //--- CSquare オブジェクトを作成し shapes[4] にそのポインタを書き込む square=new CSquare(); square.SetSide(10); shapes[4]=square; //--- ポインタ配列ができたのでサイズを取得する int total=ArraySize(shapes); //--- 配列内のポインタを全てループに渡す for(int i=0; i<5;i++) { //--- 指定されたインデックスのポインタが有効の場合 if(CheckPointer(shapes[i])!=POINTER_INVALID) { //--- 図形の種類とスクウェアをログに記録する PrintFormat("The object of type %d has the square %G", shapes[i].GetType(), shapes[i].GetArea()); } //--- ポインタの種類が POINTER_INVALID の場合 else { //--- エラーの通知 PrintFormat("Object shapes[%d] has not been initialized! Its pointer is %s", i,EnumToString(CheckPointer(shapes[i]))); } } //--- 作成した全ての動的オブジェクトは削除が必要 for(int i=0;i<total;i++) { //--- POINTER_DYNAMIC 型のポインタを持つオブジェクトのみを削除可能 if(CheckPointer(shapes[i])==POINTER_DYNAMIC) { //--- 削除の通知 PrintFormat("Deleting shapes[%d]",i); //--- オブジェクトをポインタを通して削除 delete shapes[i]; } } } |
delete 演算子を使用してオブジェクトを削除する時にはそのポインタの種類のチェックが必要なのでご注意ください。POINTER_DYNAMIC ポインタを持つオブジェクトのみが delete を使用して削除することが出来ます。他のポインタ型の場合はエラーが返されます。
しかし、ポリモーフィズムの定義には、継承時の関数の再定義に加えて、クラス内での 1 つの関数のパラメータで異なる複数の実装が含まれます。これはクラス内に同名で型及び/またパラメータの異なる関数がいくつか存在出来ることを意味します。この場合、ポリモーフィズムは関数オーバーロードを通して実装されます。
参照
標準ライブラリ