C++ 継承・隠蔽・動的生成・オーバーライド 授業資料


0. プロジェクト構成

メインループより:

"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. 継承:共通部分のまとめ

キャラに共通する情報:

これらを 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;
}

ポイント:


3. 関数の隠蔽(いんぺい)

継承では、基底クラスと同じ名前の関数を派生クラス側にも書くと、 「基底クラスの同名の関数が見えなくなる」ことがある。 この現象を 関数の隠蔽(いんぺい) という。


3-1. まずは基底と派生を見てみよう

"BaseAndDerived.cpp"
class Base
{
public:
    void Update();
};
 
class Derived : public Base
{
public:
    // ここに Update() を書いたらどうなる?
    // void Update();
};

3-2. ''Derived'' に同名の関数を実装したらどうなる?

DerivedUpdate() を追加してみる。

"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() が名前の上で見えなくなる

つまり:

この状態を 隠蔽(いんぺい) と呼ぶ。


3-5. NewPlayer の場合で確認してみよう

最初の NewPlayer には Update() が実装されていない。

"NewPlayer_NoUpdate.cpp"
NewPlayer p("Hero", ...);
p.Update();   // CharaBase::Update() が呼ばれる

ここでは、NewPlayerUpdate() が無いので、 基底クラス 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. まとめ(この章の流れ)

隠蔽は、 「派生クラスに書いた同名関数が、基底クラスの同名関数を上書きしたように見せる仕組み」 であり、派生クラス型の変数で使うと”そう見える” という現象である。