クラスの内部構造を調べるには、IDEの機能やtypeid、decltypeなどを活用すると便利です。
派生クラスのインスタンス生成時には、基底クラスのコンストラクタが先に呼び出されます。これは「合成よりも継承」の設計思想にも通じる動作で、基底部分の初期化が完了してから派生部分が構築されるため、オブジェクトの整合性が保たれます。
静的メンバへのアクセスは、インスタンス経由でもクラス名直接でも可能です:
Child obj;
std::cout << obj.staticVar; // インスタンス経由
std::cout << Child::staticVar; // クラス名経由
std::cout << obj.Base::staticVar; // 基底クラス指定
std::cout << Child::Base::staticVar; // ネストしたクラス名指定
protectedとprivateの主な違いは、派生クラスからのアクセス可否です。protectedは派生クラス内からアクセス可能ですが、privateは同一クラス内のみ許可されます。
virtualキーワードは、仮想関数テーブル(vtable)を生成し、実行時に適切な関数を呼び出す仕組みを提供します。菱形継承では、仮想継承(virtual継承)を使うことで共通基底クラスの重複インスタンスを回避できます。内部的には、各オブジェクトが仮想基底クラスへのオフセットを保持するポインタを持ち、一意のサブオブジェクトを参照します。
ポリモーフィズムの成立条件は二つ:
① 継承関係にあること
② 派生クラスが基底クラスの仮想関数をオーバーライドすること
オーバーライドとオーバーロードの違い:
- オーバーライド:関数名・引数リスト・戻り値型が完全一致(ただし戻り値の共変性は例外)
- オーバーロード:関数名のみ一致、引数リストが異なる
ポリモーフィズムの実装手順:
- 基底クラスに純粋仮想関数を宣言
- 派生クラスでその関数を再定義(
virtualは省略可) - 基底クラスポインタで派生クラスオブジェクトを操作
- 使用後は適切にメモリ解放
#include <iostream>
using namespace std;
// 抽象基底クラス:飲料のテンプレート
class BeverageTemplate {
public:
virtual void heatWater() = 0;
virtual void steep() = 0;
virtual void pourCup() = 0;
virtual void addCondiments() = 0;
void prepareBeverage() {
heatWater();
steep();
pourCup();
addCondiments();
}
};
// コーヒー実装
class CoffeeMaker : public BeverageTemplate {
public:
void heatWater() override { cout << "水を沸かす\n"; }
void steep() override { cout << "コーヒーを抽出\n"; }
void pourCup() override { cout << "カップに注ぐ\n"; }
void addCondiments() override { cout << "砂糖とミルクを追加\n"; }
};
// 紅茶実装
class TeaMaker : public BeverageTemplate {
public:
void heatWater() override { cout << "ミネラルウォーターを沸かす\n"; }
void steep() override { cout << "茶葉を浸す\n"; }
void pourCup() override { cout << "カップに注ぐ\n"; }
void addCondiments() override { cout << "レモンを加える\n"; }
};
void serveBeverage(BeverageTemplate* drink) {
drink->prepareBeverage();
delete drink;
}
int main() {
serveBeverage(new CoffeeMaker);
cout << "----------\n";
serveBeverage(new TeaMaker);
return 0;
}