Search code examples
c++polymorphismrun-time-polymorphism

Suppressed the virtual mechanism by storing object explicitly as base class?


As you'd expect, I've arrived at a question I can't answer and I can only guess.

Runtime polymorphism is the goal, using the virtual mechanism, but the result I'm getting is as if I called the method while suppressing it; like I called the base class method.

I can only guess then that I am somehow doing that, the call is from an object that is a base class, although it was constructed as a derived class. So, constructed as a derived, stored as a base.

I do store this as a static base class variable within a cpp and interfaced with multiple extern functions to access elsewhere. (perhaps, that's the gotcha?)

GameScene.h:

class GameScene {
public:
    GameScene() { SceneInit(); }
    ~GameScene() { }

    virtual void LevelInit() { } // gcc complains w/o {}
    void SceneInit();

    virtual void UpdateLevel( const float& timeDelta ) { } // gcc complains unless {} added
    void UpdateScene( const float& timeDelta );
};

extern void NewScene( const GameScene& level );
extern void UpdateScene( const float& timeDelta );

class TestLevel : public GameScene {
public:
    TestLevel() { SceneInit(); }
    // implementation here in header
    void LevelInit() override
    {
        // level-specific initialization that is apparent at runtime
    }
    void UpdateLevel( const float& timeDelta ) override
    {
        // level-specific checks and performance
        // but, to test, I simply log "This is the test level"
    }
};

class TutorialLevel : public GameScene {
public:
    TutorialLevel() { SceneInit(); }
    // implementation here in header
    void LevelInit() override
    {
        // level-specific initialization that is apparent at runtime
    }
    void UpdateLevel( const float& timeDelta )
    {
        // level-specific checks and performance
        // debug log "This is the tutorial level"
    }
};

GameScene.cpp:

#include "GameScene.h"

static GameScene currentScene; // I am now wondering if this pattern is the problem (by explicitly storing this as the base class)

extern void NewScene( const GameScene& level )
{
    currentScene = level;
}
extern void UpdateScene( const float& timeDelta )
{
    currentScene.UpdateScene( timeDelta );
}

GameScene::SceneInit()
{
    // general init
    LevelInit(); // this _properly_ calls the subclass overridden version
    // init completion
}
GameScene::UpdateScene( const float& timeDelta )
{
    // general updating and game handling

    UpdateLevel( timeDelta ); // this was _meant_ to call the overridden version, it does not
}

EntryPoint.cpp:

#include "GameScene.h"

int main()
{
    //NewScene( TestLevel() );
    NewScene( TutorialLevel() );
    float deltaTime;
    while (gameLoop)
    {
        deltaTime = SecondsSinceLastFrame(); // pseudo
        UpdateScene( deltaTime );
    }
}

So, I was following a pattern that worked with SceneInit() calling LevelInit(), which is overridden in the derived classes. If I use either derived class constructor in NewScene(), I get those LevelInit() results at runtime. I thought this would be safe to use this pattern with UpdateScene().

What I see is UpdateScene() called the GameScene::UpdateLevel(), even though it is clearly overridden in the subclasses, just like LevelInit() is.

My (wild) guess is, I am calling UpdateLevel() as if I had explicitly cast it as GameScene. :\

And maybe, this is because I store currentScene as GameScene? (but then, doesn't that defeat the purpose of having polymorphism?)

I am missing something about either storing currentScene, or calling UpdateLevel(). I've tried calling like this:

    GameScene *s = currentScene;
    s->UpdateLevel();

after reading that, as a pointer, the virtual mechanism should be finding the most-derived version of the method. (but alas...)

I wish I could have found an example here or elsewhere, but my searches pointed out problems with virtual in constructors/deconstructors, or just not using the 'virtual' keyword, etc.


Solution

  • currentScene is a GameScene. You've declared it as such. It will never be anything but a GameScene. A GameScene is not a TestLevel or a TutorialLevel; it is a GameScene. When you assign a TestLevel or TutorialLevel object to currentScene you are slicing it.

    Polymorphism in C++ only works with pointers and references. A pointer or reference to a GameScene can refer to a TestLevel or TutorialLevel object, but a GameScene object cannot be a TestLevel or TutorialLevel.

    What this boils down to is that currentScene needs to be a (smart) pointer to a GameScene instead of a GameScene. It can then point to a dynamically-allocated object of any class derived from GameScene. For example:

    static std::unique_ptr<GameScene> currentScene;
    
    extern void NewScene( std::unique_ptr<GameScene> level )
    {
        currentScene = std::move(level);
    }
    
    int main()
    {
        NewScene( std::make_unique<TutorialLevel>() );
        // ...
    }