====== 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)のほうが優先される
* ④ 基底クラスの同名関数は見えなくなる(=隠蔽)
隠蔽は、
「派生クラスに書いた同名関数が、基底クラスの同名関数を上書きしたように見せる仕組み」
であり、**派生クラス型の変数で使うと”そう見える”** という現象である。