====== C++ 継承・隠蔽・動的生成・オーバーライド 授業資料 ====== ---- ===== 0. プロジェクト構成 ===== * 共通基底クラス ''CharaBase'' * 旧プレイヤークラス ''Player'' * 敵キャラクラス ''Enemy'' * 新しいプレイヤー ''NewPlayer'' (''CharaBase'' を継承) メインループより: 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'' にまとめてある。 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_; }; void CharaBase::Update() { float dt = GetDeltaTime(); pos_.x = pos_.x + speed_.x * dt; pos_.y = pos_.y + speed_.y * dt; } ---- ===== 2. NewPlayer はこう継承している ===== class NewPlayer : public CharaBase { public: NewPlayer(string name, Vector2D pos, Vector2D speed, float radius); void SayHello(); bool GetIsSayHello() { return isSayHello_; } private: bool isSayHello_; }; 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. まずは基底と派生を見てみよう ==== class Base { public: void Update(); }; class Derived : public Base { public: // ここに Update() を書いたらどうなる? // void Update(); }; ---- ==== 3-2. ''Derived'' に同名の関数を実装したらどうなる? ==== → ''Derived'' に ''Update()'' を追加してみる。 class Derived : public Base { public: void Update(); // Base::Update() と同じ名前 }; ---- ==== 3-3. どちらが呼ばれるのか試してみる ==== Derived d; d.Update(); // さて、どっちの Update() が呼ばれる? → 呼ばれるのは **Derived::Update()** (基底クラス Base の Update() は呼ばれない) ---- ==== 3-4. なぜこうなるのか? ==== 同じ名前の関数が派生クラスに書かれたことで、 **基底クラスの Update() が名前の上で見えなくなる**。 つまり: * ''Derived'' に関数を書く * → ''Base'' の同名関数は「隠れる」 * → 呼ばれるのは派生クラス側の関数になる この状態を **隠蔽(いんぺい)** と呼ぶ。 ---- ==== 3-5. NewPlayer の場合で確認してみよう ==== 最初の ''NewPlayer'' には ''Update()'' が実装されていない。 NewPlayer p("Hero", ...); p.Update(); // CharaBase::Update() が呼ばれる ここでは、''NewPlayer'' に ''Update()'' が無いので、 基底クラス ''CharaBase'' のものがそのまま使われる。 ---- ==== 3-6. NewPlayer に Update() を実装するとどうなる? ==== class NewPlayer : public CharaBase { public: void Update(); // ← 自分で実装 }; これを呼ぶと: NewPlayer p("Hero", ...); p.Update(); // NewPlayer::Update() が呼ばれる → ''CharaBase::Update()'' は呼ばれない。 → つまり、''CharaBase::Update()'' が **隠蔽** されている。 ---- ==== 3-7. まとめ(この章の流れ) ==== * ① NewPlayer に Update() を書く * ② 基底クラスの Update() と同名になる * ③ 呼び出すと継承先(NewPlayer)のほうが優先される * ④ 基底クラスの同名関数は見えなくなる(=隠蔽) 隠蔽は、 「派生クラスに書いた同名関数が、基底クラスの同名関数を上書きしたように見せる仕組み」 であり、**派生クラス型の変数で使うと”そう見える”** という現象である。