Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheritance with static methods/fields c++

I have a class of Enemy that i want to be the base class for all enemy types and also pure abstract one. At this point, all of its members and methods should be shared by the derived classes. Especially, there is the method loadTexture that uses the static member texture.

    class Enemy
    {
        int hp;
        int damage;
        //
        // all other fields
        //
        static TextureClass* texture; //needs to be static because every instance
                                      //of enemy uses the same texture
    public:
        static void loadTexture()
        {
            CreateTextureFromFile("somefilepath",&texture); //needs to be static
          // because textures are loaded before any instance is crated
        }
        void draw()
        {
            DrawToScreen(&texture, ...many data members passed here...);
        }
    };

    TextureClass* Enemy::texture = nullptr;

Now, if i would like to make the Enemy abstract and create different enemy types, the obvious choice would be inheritance. So i create:

class EnemyA : Enemy
{
    static TextureClass* texture;
};

class EnemyB : Enemy
{
    static TextureClass* texture;
};

But now, how do i load the texture for each of those? I can't use loadTexture defined in base obviously. So the only choice besides writing the same method X times (where X is the number of enemy types) is to remove loadTexture from base and create a global function, right? Same goes for draw, i would need to redefine it for every derived, even though it would look exactly the same...

TL;DR The loadTexture and draw have exact same body regardless of the enemy type, but they use the static field that is different in each one. Is there a way to define something like an uniform method, that when called in derived class would use the field from the derived, not the base?

Thank You for any answers.

like image 406
tomi.lee.jones Avatar asked Feb 15 '23 00:02

tomi.lee.jones


1 Answers

As mentioned by BЈовић, if you want to use inheritance to have the loadTexture() and draw() methods reference the right Texture for your class, the answer probably lies in using CRTP to implement your Enemy class, like follows:

template<typename TDerivedEnemy>
class Enemy
{
    int hp;
    int damage;
    //
    // all other fields
    //
    static TextureClass* texture; //needs to be static because every instance
                                  //of enemy uses the same texture
public:
    static void loadTexture()
    {
        CreateTextureFromFile("somefilepath",&texture); //needs to be static
      // because textures are loaded before any instance is crated
    }
    void draw()
    {
        DrawToScreen(&texture, ...many data members passed here...);
    }
};

Once that is done, you can declare your concrete enemy classes like follows:

class EnemyA : public Enemy<EnemyA>
{
public:
    typedef Enemy<EnemyA> tBase;
    ...
};

class EnemyB : public Enemy<EnemyB>
{
public:
    typedef Enemy<EnemyB> tBase;
    ...
};

then, in your implementation file, you also need to define the static variables for both EnemyA and EnemyB:

TextureClass* EnemyA::tBase::texture = nullptr;
TextureClass* EnemyB::tBase::texture = nullptr;

The name "Curiously Recurring Template Pattern" comes from the fact that EnemyA and EnemyB inherit from a template class where they already are parameter templates, hence recursion.

Edit: as discussed in the comment, this approach leads to EnemyA and EnemyB having no common base class, which makes it impossible to refer to them in a uniform way, i.e. you can't declare for instance a

std::vector< EnemyA* OR EnemyB* ??? > enemies;

because there simply isn't a common base class. To get around this, you can declare a common abstract base class like follows:

class EnemyBase {
public:
    virtual void draw() = 0;
}

then make your template implementation inherit from it:

template<typename TDerivedEnemy>
class Enemy : public EnemyBase
{
    ...
};

Which allows you to do this:

std::vector<EnemyBase*> enemies;

//fill the enemies vector
...

for (auto enemy : enemies)
{
    enemy->draw();
}
like image 177
Martin J. Avatar answered Feb 28 '23 06:02

Martin J.