C++における多態性(Polymorphism)の完全かつ包括的な解説
C++における「多態性(Polymorphism)」は、オブジェクト指向プログラミング(OOP)の重要な特徴の一つです。多態性により、同じインターフェースで異なる型のオブジェクトを扱うことができ、プログラムの柔軟性や再利用性が向上します。この概念は、主に「メソッドのオーバーライド」や「オーバーロード」など、異なる実行時の挙動を実現するために使用されます。この記事では、C++における多態性について、詳細に解説し、その種類や使い方を説明します。
1. 多態性の基本概念
多態性という言葉は、ギリシャ語の「ポリ(多)」と「モルフ(形)」から来ており、文字通り「多くの形態」を意味します。プログラムにおける多態性は、同じ操作が異なるデータ型に対して異なる動作をすることを指します。C++では、これを実現するために「関数のオーバーロード」や「仮想関数(virtual function)」などの技法が使われます。

2. 多態性の種類
C++における多態性は主に2種類に分類されます。
2.1 コンパイル時の多態性(静的多態性)
静的多態性は、プログラムのコンパイル時に決定される多態性です。これを実現するために使用される主な技法は「関数のオーバーロード」や「テンプレート」です。コンパイル時に異なる関数が呼び出されるため、動的な決定が行われません。
-
関数のオーバーロード: 同じ名前の関数を異なる引数の型や数で定義することによって、異なる動作をさせることができます。
cpp#include
using namespace std; class Printer { public: void print(int i) { cout << "整数: " << i << endl; } void print(double d) { cout << "実数: " << d << endl; } void print(string s) { cout << "文字列: " << s << endl; } }; int main() { Printer p; p.print(10); // 整数: 10 p.print(3.14); // 実数: 3.14 p.print("Hello"); // 文字列: Hello return 0; }
この例では、print
という同じ名前の関数が異なる引数の型によってオーバーロードされています。コンパイル時に適切な関数が選ばれるため、静的多態性が実現されます。
2.2 実行時の多態性(動的多態性)
動的多態性は、プログラムの実行時に決定される多態性で、主に「仮想関数」を使って実現されます。これにより、異なるオブジェクトの型によって同じ関数呼び出しが異なる動作をするようになります。
-
仮想関数: 基底クラスで定義された関数を仮想関数として宣言することで、派生クラスでオーバーライド(再定義)できるようにします。このとき、オブジェクトの実際の型に基づいて適切な関数が実行されます。
cpp#include
using namespace std; class Shape { public: virtual void draw() { // 仮想関数 cout << "図形を描画しています。" << endl; } }; class Circle : public Shape { public: void draw() override { // オーバーライド cout << "円を描画しています。" << endl; } }; class Square : public Shape { public: void draw() override { // オーバーライド cout << "四角形を描画しています。" << endl; } }; int main() { Shape* shape1 = new Circle(); Shape* shape2 = new Square(); shape1->draw(); // 円を描画しています。 shape2->draw(); // 四角形を描画しています。 delete shape1; delete shape2; return 0; }
この例では、Shape
クラスに仮想関数draw
を定義し、Circle
クラスとSquare
クラスでそれをオーバーライドしています。Shape
型のポインタを使って、実行時にどの関数が呼び出されるかが決定されます。これにより、動的多態性が実現されます。
3. 仮想関数と動的バインディング
仮想関数が動的多態性を実現するカギです。仮想関数が呼び出されると、C++は動的バインディング(遅延バインディング)を行い、実際のオブジェクトの型に応じた関数を選択します。このため、実行時にオブジェクトの型に基づいて異なる動作をさせることができます。
例えば、上記のコードでは、Shape*
型のポインタを使ってCircle
型やSquare
型のオブジェクトを操作しています。仮想関数draw
がオーバーライドされているため、Shape*
ポインタが指すオブジェクトの型に応じて、Circle
やSquare
のdraw
メソッドが呼び出されます。
4. 親クラスと派生クラスの関係
多態性を活用するためには、親クラス(基底クラス)と派生クラス(サブクラス)の関係が重要です。派生クラスは親クラスのインターフェースを拡張したり、オーバーライドしたりすることができます。これにより、異なる型のオブジェクトを同一のインターフェースで扱うことができ、プログラムの柔軟性が向上します。
5. 多態性の利点
多態性を活用することで、以下のような利点があります。
-
コードの再利用性: 共通のインターフェースを使用することで、異なる型のオブジェクトを同じコードで処理できます。
-
拡張性: 新しい型を追加しても、既存のコードを変更することなく、新しい動作を追加できます。
-
可読性と保守性: コードの可読性が向上し、保守が容易になります。
6. 注意点と最適化
-
仮想関数のオーバヘッド: 仮想関数を使用すると、関数呼び出しに若干のオーバーヘッドが発生します。このため、パフォーマンスが重要な場合には注意が必要です。
-
純粋仮想関数: 基底クラスで純粋仮想関数(
= 0
)を定義することで、派生クラスに実装を強制することができます。これにより、抽象クラスとして機能させることができます。cppclass Shape { public: virtual void draw() = 0; // 純粋仮想関数 };
7. 結論
C++における多態性は、オブジェクト指向プログラミングの中でも非常に重要な特徴であり、プログラムを柔軟で拡張可能なものにします。コンパイル時の多態性と実行時の多態性をうまく使い分けることで、効率的で再利用可能なコードを作成することができます。多態性を理解し、適切に活用することで、C++プログラミングのスキルを一段と向上させることができるでしょう。