C++ 継承・隠蔽・動的生成・オーバーライド 授業資料
0. プロジェクト構成
- 共通基底クラス
CharaBase - 旧プレイヤークラス
Player - 敵キャラクラス
Enemy - 新しいプレイヤー
NewPlayer(CharaBaseを継承)
メインループより:
- "theMain.cpp (抜粋)"
NewPlayer nPlayer("NewHero", Vector2D(50,250), Vector2D(1.0f,0), 1.0f); Enemy enemy("Monster", Vector2D(300,250), Vector2D(-5.0f,0), 1.0f); void Update() { nPlayer.Update(); enemy.Update(); } void Draw() { nPlayer.Draw(); enemy.Draw(); }
1. 継承:共通部分のまとめ
キャラに共通する情報:
name_:名前pos_:位置speed_:速度radius_:半径Update():移動処理Draw():描画処理
これらを CharaBase にまとめてある。
- "CharaBase.h (抜粋)"
class CharaBase { public: CharaBase(string name, Vector2D pos, Vector2D speed, float radius); void Update(); void Draw(); void SetName(string name); void SetPosition(Vector2D pos); string GetName() const; Vector2D GetPosition() const; void SetSpeed(Vector2D speed); Vector2D GetSpeed() const; void SetRadius(float radius); double GetRadius() const; void SetChara(string n, Vector2D p, Vector2D v, double r); private: string name_; Vector2D pos_; Vector2D speed_; float radius_; };
- "CharaBase.cpp (Update 部分)"
void CharaBase::Update() { float dt = GetDeltaTime(); pos_.x = pos_.x + speed_.x * dt; pos_.y = pos_.y + speed_.y * dt; }
2. NewPlayer はこう継承している
- "NewPlayer.h"
class NewPlayer : public CharaBase { public: NewPlayer(string name, Vector2D pos, Vector2D speed, float radius); void SayHello(); bool GetIsSayHello() { return isSayHello_; } private: bool isSayHello_; };
- "NewPlayer.cpp"
NewPlayer::NewPlayer(string name, Vector2D pos, Vector2D speed, float radius) : CharaBase(name, pos, speed, radius) , isSayHello_(false) { } void NewPlayer::SayHello() { isSayHello_ = true; }
ポイント:
NewPlayerのコンストラクタでCharaBaseを初期化- 共通部分は
CharaBaseが担当 - NewPlayer 独自の
isSayHello_のみ自分で管理
3. 関数の隠蔽(いんぺい)
継承では、基底クラスと同じ名前の関数を派生クラス側にも書くと、 「基底クラスの同名の関数が見えなくなる」ことがある。 この現象を 関数の隠蔽(いんぺい) という。
3-1. まずは基底と派生を見てみよう
- "BaseAndDerived.cpp"
class Base { public: void Update(); }; class Derived : public Base { public: // ここに Update() を書いたらどうなる? // void Update(); };
3-2. ''Derived'' に同名の関数を実装したらどうなる?
→ Derived に Update() を追加してみる。
- "DerivedUpdate.cpp"
class Derived : public Base { public: void Update(); // Base::Update() と同じ名前 };
3-3. どちらが呼ばれるのか試してみる
- "CallUpdate.cpp"
Derived d; d.Update(); // さて、どっちの Update() が呼ばれる?
→ 呼ばれるのは Derived::Update() (基底クラス Base の Update() は呼ばれない)
3-4. なぜこうなるのか?
同じ名前の関数が派生クラスに書かれたことで、 基底クラスの Update() が名前の上で見えなくなる。
つまり:
Derivedに関数を書く- →
Baseの同名関数は「隠れる」 - → 呼ばれるのは派生クラス側の関数になる
この状態を 隠蔽(いんぺい) と呼ぶ。
3-5. NewPlayer の場合で確認してみよう
最初の NewPlayer には Update() が実装されていない。
- "NewPlayer_NoUpdate.cpp"
NewPlayer p("Hero", ...); p.Update(); // CharaBase::Update() が呼ばれる
ここでは、NewPlayer に Update() が無いので、
基底クラス CharaBase のものがそのまま使われる。
3-6. NewPlayer に Update() を実装するとどうなる?
- "NewPlayer_Hide.cpp"
class NewPlayer : public CharaBase { public: void Update(); // ← 自分で実装 };
これを呼ぶと:
- "CallNewPlayer.cpp"
NewPlayer p("Hero", ...); p.Update(); // NewPlayer::Update() が呼ばれる
→ CharaBase::Update() は呼ばれない。
→ つまり、CharaBase::Update() が 隠蔽 されている。
3-7. まとめ(この章の流れ)
- ① NewPlayer に Update() を書く
- ② 基底クラスの Update() と同名になる
- ③ 呼び出すと継承先(NewPlayer)のほうが優先される
- ④ 基底クラスの同名関数は見えなくなる(=隠蔽)
隠蔽は、 「派生クラスに書いた同名関数が、基底クラスの同名関数を上書きしたように見せる仕組み」 であり、派生クラス型の変数で使うと”そう見える” という現象である。