オブジェクト指向プログラミング(OOP)の真の力を解放する時が来ました!💪 この章では、OOPの強力な柱である「継承 (Inheritance)」と「ポリモーフィズム (Polymorphism) / 多態性」を学びます。これらの概念をマスターすることで、コードの再利用性を高め、柔軟で拡張性の高いプログラムを設計できるようになります。
継承とは、既存のクラス(親クラスまたは基底クラスと呼びます)の機能を引き継いで、新しいクラス(子クラスまたは派生クラスと呼びます)を作成する仕組みです。これにより、共通の機能を何度も書く必要がなくなり、コードの重複を避けられます。
例えば、「動物」という大まかなクラスがあり、その特徴を引き継いで「犬」や「猫」といった具体的なクラスを作ることができます。「犬」も「猫」も「動物」が持つ「食べる」という共通の機能を持っていますよね。
C++では、クラス名の後に : public 親クラス名
と書くことで継承を表現します。
この例では、Dog
クラスはAnimal
クラスを継承しています。そのため、Dog
クラスのオブジェクト my_dog
は、Animal
クラスで定義されたメンバ変数 name
やメンバ関数 eat()
を、まるで自分のものであるかのように利用できます。
継承の最も強力な側面は、**ポリモーフィズム(多態性)**を実現できることです。ポリモーフィズムとは、ギリシャ語で「多くの形を持つ」という意味で、プログラミングにおいては「同じインターフェース(指示)で、オブジェクトの種類に応じて異なる振る舞いをさせる」ことを指します。
これを実現するのが 仮想関数 (virtual function) です。親クラスの関数宣言の前に virtual
キーワードを付けると、その関数は仮想関数になります。
親クラスのポインタや参照は、子クラスのオブジェクトを指すことができます。このとき、呼び出された仮想関数は、ポインタが指しているオブジェクトの実際の型に基づいて決定されます。
言葉だけでは難しいので、コードで見てみましょう。
make_animal_speak
関数は Animal*
型の引数を取りますが、Dog
オブジェクトやCat
オブジェクトのアドレスを渡すことができています。そして、animal->speak()
を呼び出すと、animal
ポインタが実際に指しているオブジェクトの speak()
が実行されます。これがポリモーフィズムです。もし Animal
クラスの speak()
に virtual
が付いていなければ、どのオブジェクトを渡しても Animal
の speak()
が呼ばれてしまいます。
先ほどの例で override
というキーワードが登場しましたね。これはC++11から導入されたもので、子クラスの関数が親クラスの仮想関数を上書き(オーバーライド)する意図があることを明示するためのものです。
override
を付けておくと、もし親クラスに対応する仮想関数が存在しない場合(例えば、関数名をタイプミスした場合など)に、コンパイラがエラーを検出してくれます。
class Dog : public Animal {
public:
// もし親クラスのspeakがvirtualでなかったり、
// speek() のようにタイプミスしたりすると、コンパイルエラーになる。
void speak() override {
std::cout << "Woof!" << std::endl;
}
};
意図しないバグを防ぐために、仮想関数をオーバーライドする際は必ず override
を付ける習慣をつけましょう。
時には、「具体的な実装を持たず、子クラスに実装を強制するための設計図」としてのみ機能するクラスを定義したい場合があります。これが抽象クラス (Abstract Class) です。
抽象クラスは、純粋仮想関数 (pure virtual function) を1つ以上持つクラスです。純粋仮想関数は、末尾に = 0
を付けて宣言します。
virtual void function_name() = 0; // これが純粋仮想関数
抽象クラスは以下の特徴を持ちます。
Shape
クラスは「図形なら描画できるはずだ」というインターフェース(契約)を定義し、具体的な描画方法は子クラスである Circle
や Square
に任せています。このように、抽象クラスはプログラムの骨格となる設計を強制するのに非常に役立ちます。
(子クラス) : public (親クラス)
のように書きます。
virtual
): ポリモーフィズムを実現するための鍵です。親クラスの関数に virtual
を付けると、ポインタや参照経由で呼び出した際に、オブジェクトの実際の型に応じた関数が実行されます。override
): 子クラスで親クラスの仮想関数を上書きする意図を明示します。コンパイラがチェックしてくれるため、安全性が向上します。virtual ... = 0;
) を持つクラスです。インスタンス化できず、継承されるための設計図として機能します。Vehicle
という親クラスを作成し、move()
というメンバ関数を持たせましょう。次に、Vehicle
を継承して Car
クラスと Motorcycle
クラスを作成し、それぞれが独自の move()
の振る舞いをするようにオーバーライドしてください。
main
関数では、Vehicle
のポインタの配列を作成し、Car
と Motorcycle
のオブジェクトを格納して、ループでそれぞれの move()
を呼び出してください。
Employee
という抽象クラスを定義してください。このクラスは、従業員の名前を保持し、給与を計算するための純粋仮想関数 calculate_salary()
を持ちます。
次に、Employee
を継承して、FullTimeEmployee
(月給制)と PartTimeEmployee
(時給制)の2つのクラスを作成します。それぞれのクラスで calculate_salary()
を具体的に実装してください。
main
関数で、それぞれのクラスのインスタンスを作成し、給与が正しく計算されることを確認してください。