1

I handed in a remake of an old game in C++ using SDL2 for a programming course that I took, I passed but the professor left me some feedback and there is one part that I couldn't understand. It made sense when he said it and when I tried implementing it I got stuck.

I have an Enemy class which is an abstract class and then many different enemies that inherit from that class. The issue is that when I am creating an enemy I have to pass in its constructor my ProjectileManager* instance that I have created in my main controlling file called Game.cpp. He said that since all enemies need the ProjectileManager it would be smarter to just add it in the Enemies class as protected and have the other children inherit it.

The issue is that the instance is created in the Game.cpp and I have to pass that specific instance since that is the one that I am Drawing and Updating for. Is there a way to do that?

Edit: Here is some of the code
Game.cpp:

void Game::Initialize()
{
    m_pProjectileManager = new ProjectileManager();
}

The projectile manager is what Updates and Draws all the projectiles as well as their hit logic etc. It has an array that manages all the projectiles. Every time an enemy wants to shoot it would access the projectile manager (that I passed in its constructor) and trigger the shoot function of the specific enemy:

void Plant::Shoot(const Point2f& target)
{
    if (m_CanShoot)
    {
        m_CurrentState = Plant::PlantState::shooting;
        m_pProjectileManager->AddProjectile((new PlantProjectile(Point2f{ m_Shape.left, m_Shape.bottom }, target)));
        m_CanShoot = false;
        m_TimeSinceLastShot = 0;
    }
}

Finally this is the Enemy class that I have right now:

class Enemies
{
public:
    virtual ~Enemies() = 0;
    virtual void Update(float elapsedSec, const Point2f& playerPos, Level& level) = 0;
    virtual void Draw() const = 0;
    virtual Rectf GetShape() const = 0;
    virtual float GetHealth() const = 0;
    virtual void Damage(float damage) = 0;

protected:
    ProjectileManager* m_pProjectileManager;
};
Alex
  • 75
  • 7
  • With a pointer. – sweenish Aug 18 '22 at 16:33
  • If the `ProjectileManager*` instance is created in your `main`, or some other bigger enclosing scope `Game.cpp`, and needs to be shared between different enemies, then the professor is wrong, and you should pass that ptr in the `Enemy` constructor. Anyway, code please, otherwise we can speculate and misunderstand each other. – pptaszni Aug 18 '22 at 16:33
  • In any case, you can't create instances of abstract classes. – Ted Lyngmo Aug 18 '22 at 16:35
  • 2
    You cannot create abstract classes. That's what makes them abstract. – Sam Varshavchik Aug 18 '22 at 16:38
  • The terminology in the title "create an instance of a variable in an abstract class" is very close to "create an instance of an abstract class" which is a totally different question and answer. But for this question, you'll probably do well to show some code of what you want to do. As either you'll figure it out when writing the code or else it'll be easier to help with what specifically is tripping you up. – TheUndeadFish Aug 18 '22 at 16:38
  • @pptaszni It's quite a larger project and I'm not sure what part of to post exactly but I'll try – Alex Aug 18 '22 at 16:39
  • Ummm just `class Enemy` and let's say `class BlackDragon: public Enemy` + some small usecase should be sufficient, only the relevant parts with `ProjectileManager*`, no need to see everything. There is no such thing as super large project where small problem cannot be isolated to small example. – pptaszni Aug 18 '22 at 16:42
  • @pptaszni I think I added sufficient code for people to understand better. – Alex Aug 18 '22 at 16:52
  • It all depends where you create your `Enemy`. (Is a `Plant` an `Enemy` or is there a `Zombie` class somewhere?) You can pass the `ProjectileManager` to the instance of the `Enemy` when you create the instance of whatever class inherits from `Enemy`. – Wyck Aug 18 '22 at 16:58
  • Is _there only one_ `ProjectileManager` instance in your entire application? If so, you can make the instance `static` in `Enemy` – Chad Aug 18 '22 at 17:02
  • @Alex Why does `Game` have a raw owning pointer to a `ProjectileManager`? Why isn't it a normal member variable: `ProjectileManager m_pProjectileManager;` - no need to `new` anything. – Ted Lyngmo Aug 18 '22 at 19:25

1 Answers1

1

If there is only ever one ProjectileManager object in the entire game, there are two "easy" ways to accomplish this, and both have some drawbacks.

First, you could make it a singleton:

class ProjectileManager
{
    // stuff....
    static ProjectileManager* instance()
    {
        static ProjectileManager one;
        return &one;
    }
};

// Game.cpp
void Game::Initialize()
{
    // m_pProjectileManager = new ProjectileManager();
    m_pProjectileManager = ProjectileManager::instance();
}

// Enemy impl
void Plant::Shoot(const Point2f& target)
{
    if (m_CanShoot)
    {
        m_CurrentState = Plant::PlantState::shooting;
        ProjectileManager::instaince()->AddProjectile((new PlantProjectile(Point2f{ m_Shape.left, m_Shape.bottom }, target)));
        m_CanShoot = false;
        m_TimeSinceLastShot = 0;
    }
}

Or you could make it static in Enemy, and provide a function to set it:

class Enemy
{
public:
    static SetProjectileManager(ProjectileManager* projectile_manager)
    {
        m_pProjectileManager = projectile_manager;
    }

protected:
    static ProjectileManager* m_pProjectileManager;
};

// Game.cpp
void Game::Initialize()
{
    m_pProjectileManager = new ProjectileManager();
    Enemy::SetProjectileManager(m_pProjectileManager);
}

Without seeing the entire codebase it's hard to know which is the better (of these two) recommendations. Neither is perfect by any means. Singletons have a lot of drawbacks and I hesitate to recommend them much anymore. Static class members are perhaps less of a code smell.

In any case, the best path forward is likely a refactor to pass to the Enemy constructor.

Some further reading:

What are drawbacks or disadvantages of singleton pattern?

Why using static namespace/class member variables is a bad idea in C++?

Chad
  • 18,706
  • 4
  • 46
  • 63
  • I haven't been taught singletons nor have I used them but we have covered the static keyword so I think I'll do some reading on that and go for that route. Thanks for providing two solutions as well! – Alex Aug 18 '22 at 17:18
  • Yep I also think that the best way would be to pass `ProjectileManager*` to `Enemy` constructor, this will allow easy unit-testing and still allows to use singleton if you decide it's fine. – pptaszni Aug 19 '22 at 09:40