C++ ポリモーフィズム


ポリモーフィズムは文字通り複数の形式を意味します。ポリモーフィズムは、クラス間に階層があり、クラスが継承を通じて関連付けられている場合に使用されます。

C++ ポリモーフィズムとは、メンバー関数が呼び出されたときに、関数を呼び出すオブジェクトの型に応じて異なる関数が実行されることを意味します。

以下の例では、基本クラス Shape は次のように 2 つのクラスに派生します:

#include <iostream> 
using namespace std;
 
class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};
class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
};
class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
};
// 程序的主函数
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);

   // 存储矩形的地址
   shape = &rec;
   // 调用矩形的求面积函数 area
   shape->area();

   // 存储三角形的地址
   shape = &tri;
   // 调用三角形的求面积函数 area
   shape->area();
   
   return 0;
}

上記のコードがコンパイルされて実行されると、次の結果が生成されます:

Parent class area
Parent class area

エラー出力の理由は、呼び出しです。関数 area() は、コンパイラによって基本クラスのバージョンに設定されます。これは、静的ポリモーフィズム、または 静的リンク と呼ばれます。関数呼び出しは、プログラムが実行される前に準備されます。 area() 関数はプログラムのコンパイル中にすでに設定されているため、これは早期バインディングと呼ばれることもあります。

ここで、プログラムを少し変更して、以下に示すように、Shape クラスで area() の宣言の前にキーワード virtual を配置します。

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};

変更後、前のコード例がコンパイルされて実行されるとき。これにより、次の結果が生成されます:

Rectangle class area
Triangle class area

この時点で、コンパイラーはポインターの型ではなく、その内容を調べます。したがって、*shapeにはクラスtriとrecのオブジェクトのアドレスが格納されているため、それぞれのarea()関数が呼び出されます。

ご覧のとおり、各サブクラスには関数 area() の独立した実装があります。これが、ポリモーフィズムの一般的な使用方法です。ポリモーフィズムを使用すると、名前が同じで実装が異なる関数を含む複数の異なるクラスを作成でき、関数のパラメーターを同じにすることもできます。

仮想関数

仮想関数は、キーワードvirtualを使用して基本クラスで宣言された関数です。基本クラスで定義された仮想関数を派生クラスで再定義するときは、その関数に静的にリンクしないようにコンパイラーに指示します。

私たちが望んでいるのは、プログラムのどの時点でも、呼び出されるオブジェクトのタイプに基づいて呼び出される関数を選択できることです。この操作は、動的リンク、または遅延バインディングと呼ばれます。

純粋な仮想関数

派生クラスで関数を再定義することがオブジェクトにとってより適切に機能するように、基底クラスで仮想関数を定義したい場合がありますが、実装のために基底クラスで意味のある仮想関数を与えることはできません、現時点では純粋な仮想関数が使用されます。

基本クラスの仮想関数 area() を次のように書き換えることができます:

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // pure virtual function
      virtual int area() = 0;
};

= 0 は、関数に本体がなく、上記の仮想関数が 純粋な仮想関数 であることをコンパイラーに伝えます。