2

Apologies if the title is incorrect, I couldn't think of better wording. I have the following code

template <class E>
void ResolveEntityHit(E& entity, Projectile& projectile) {
    static_assert(std::is_base_of<Entity, E>::value, "entity must be of type Entity");

    entity.DealDamage(projectile.GetProjectileDamage());

    if (entity.IsDead()) {
        DestroyEntity(entity); //here is the problem
    }

    m_projectileManager->DestroyProjectile(projectile);
}

void DestroyEntity(Enemy & enemy)
{
    m_enemyManager->DestroyEnemy(enemy);
}

void DestroyEntity(Bunker & bunker)
{
    m_bunkerManager->DestroyBunker(bunker);
}

I'm trying to avoid using dynamic_cast as I have read that this isn't good practice. I'm trying to keep the ResolveEntityHit as basic as possible that can accept multiple types, but then I would like to branch off and do different things depending on which type the entity is. For example, I have my entities separated into different classes, and each class is responsible for removing/adding entities, so I would need to call the function on the correct manager to remove the entity.

The code above doesn't compile and I get error C2664: 'void DestroyEntity(Bunker &)': cannot convert argument 1 from 'E' to 'Enemy &'

Hopefully it's clear what I'm trying to achieve, but I'm asking is there a better way to do this in terms of design/architecture and without the use of dynamic_cast? Possibly through using templates?

  • I cannot reproduce your failure, see rextester.com/QMZC93111. Basically here you picking the DestroyEntity statically. – max630 Jan 6 at 19:02
  • @max630 I think this is closer to what I am using rextester.com/LQIE39089 and this produces the error – jjmcc Jan 6 at 19:11
  • If you are passing object as a base class to ResolveEntityHit() then template there would not work, You could as well declare it as ResolveEntityHit(Entity& entity...) – max630 Jan 6 at 19:14
3

Your code would work if E were a concrete Entity subtype, e.g Enemy or Bunker. However, if E is the Entity base type then the overload cannot be selected. Ideally, just call the template function with the a suitable subtype.

If you need to be able to deal with E=Entity, you might as well get rid of the template. You can instead use the visitor pattern so that the Entity chooses the correct overload. First, define a visitor interface:

struct Visitor {
  virtual ~Visitor() {}
  virtual void VisitEnemy(Enemy&) = 0;
  virtual void VisitBunker(Bunker&) = 0;
};

Next, make the Entity visitable:

class Entity {
  ...
  virtual void Accept(Visitor&) = 0;
  ...
};

And implement the method in all base classes. For example:

class Enemy : public Entity {
  ...
  void Accept(Visitor& v) override { v.VisitEnemy(*this); }
};

We can then define a Visitor that destroys an Entity:

class DestroyingVisitor : Visitor {
  Manager<Enemy> m_enemyManager;
  Manager<Bunker> m_bunkerManager;

public:

  DestroyingVisitor(...) ...

  void VisitEnemy(Enemy& enemy) override {
    m_enemyManager->DestroyEnemy(enemy);
  }

  void VisitBunker(Bunker& bunker) override {
    m_bunkerManager->DestroyBunker(bunker);
  }
};

Finally, your resolve method can be rewritten to use the visitor:

void ResolveEntityHit(Entity& entity, Projectile& projectile) {
    entity.DealDamage(projectile.GetProjectileDamage());

    if (entity.IsDead()) {
        // DestroyEntity(entity);
        entity.Accept(DestroyingVisitor(...));
    }

    m_projectileManager->DestroyProjectile(projectile);
}

The conceptual problem at the root of the question is that you are mixing object oriented techniques with templates. Templates rely on knowing a specific type at compile time, whereas the point of object oriented techniques is to abstract over the concrete type so that the dynamic type is only known at runtime. In code like this

unique_ptr<Entity> e = make_unique<Enemy>();
ResolveEntityHit(*e, projectile);

the dynamic type of the *e object is Enemy. But the compiler only knows that its static type is Entity. Therefore, the template parameter E was deduced to be Entity, written explicitly as ResolveEntityHit<Entity>(...). If however you had provided Enemy as the template parameter then the ResolveEntityHit function would have compiled correctly, but the call to that function would have been a type error.

To get the concrete type back from a polymorphic object, techniques like the visitor pattern can be used. A related pattern is the virtual copy constructor. We basically have to put all operations that need to know the dynamic type of an object into that object. The visitor pattern in particular can be helpful here because we need to make an object visitable once and can reuse the visitor interface for multiple operations. But this comes at the tradeoff that you cannot simply add another Entity subtype without updating all of your visitors.

  • Some would say the "you cannot simply add another Entity subtype without updating all of your visitors" is at least neutral, as it makes it harder to make an Entity that doesn't have all the required operations. – Caleth Jan 7 at 9:55
2

Do entities contain some sort of reference to their managers? If so, you could instead call entity->DestroySelf() and the entity would simply call manager->DestroyEntity(self) or something along those lines.

Or if they don't keep references to their managers (maybe because they can be owned by more than one?), then you could have a function which searches each manager for the entity. So you might have the managers stored in a vector of managers and call each one like this:

for (auto& nextManager : managers)
{
    if (nextManager.contains(entity))
    {
        nextManager.destroyEntity(entity);
    }
}
  • Both ideas seem to solve my issue but I like the first idea. I'll give that a go and see how it turns out! Thank you – jjmcc Jan 6 at 18:31

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.