C++ポリモーフィズム完全理解:仮想関数と純粋仮想関数
ポリモーフィズム(多態性) とは、多くの形を持つことを意味します。通常、ポリモーフィズムはクラスの階層があり、それらが継承によって関連付けられているときに発生します。
C++のポリモーフィズムとは、メンバ関数の呼び出しが、その関数を呼び出すオブジェクトの型に応じて、異なる関数を実行することを意味します。
基底クラスが他の2つのクラスによって派生されている例を考えます:
Live Demo#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);
// Rectangleのアドレスを格納
shape = &rec;
// rectangleのareaを呼び出し
shape->area();
// Triangleのアドレスを格納
shape = &tri;
// triangleの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
今度は、コンパイラはポインタの型ではなく、その中身を見ます。したがって、tri と rec クラスのオブジェクトのアドレスが *shape に格納されているため、それぞれの area() 関数が呼び出されます。
ご覧のように、各子クラスは area() 関数の別々の実装を持っています。これが ポリモーフィズム の一般的な使用方法です。同じ名前、同じパラメータを持つ関数ですが、異なる実装を持つ異なるクラスがあります。
仮想関数
仮想 関数とは、基底クラスで virtual キーワードを使って宣言された関数です。基底クラスで仮想関数を定義し、派生クラスで別のバージョンを持たせることで、この関数に対して静的リンケージを望まないことをコンパイラに伝えます。
私たちが望むのは、プログラムの任意の時点で呼び出される関数の選択が、それが呼び出されるオブジェクトの種類に基づくことです。この種の操作は 動的リンケージ または 後期バインディング と呼ばれます。
純粋仮想関数
基底クラスに仮想関数を含め、派生クラスでそのクラスのオブジェクトに適するよう再定義させたいが、基底クラスではその関数に意味のある定義を与えられない場合があります。
基底クラスの仮想関数 area() を以下のように変更できます:
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) {
width = a;
height = b;
}
// 純粋仮想関数
virtual int area() = 0;
};
= 0 は、その関数に本体がないことをコンパイラに伝え、上記の仮想関数は 純粋仮想関数 と呼ばれます。
C言語
- C++演算子完全ガイド:算術・代入・関係・論理・ビット演算子と実例
- C++配列を関数に渡す完全ガイド:1次元・多次元配列の実例付き
- C++クラスとオブジェクト完全ガイド:オブジェクト指向プログラミングの実践
- C++フレンド関数とフレンドクラス:アクセス制御のマスター
- C++クラステンプレート完全ガイド:実践的なジェネリックプログラミング
- C++演算子オーバーロード:コード例付き実践ガイド
- C++ストレージクラス完全解説:スコープ、ライフタイム、使い方
- C++オーバーロード完全マスター:関数と演算子のオーバーロード
- C++データ抽象化完全マスター:インターフェースと実装の分離
- C++データカプセル化:コードを守る仕組みと実装例
- C++インターフェース完全理解:抽象クラスと純粋仮想関数