nyan
nyan

Reputation: 23

Problem about templates and virtual function

I have a problem with templates - virtual function mismatch.

First of all, i have a parent class named Unit and 2 child classes named Hero and Monster. These 2 classes have both 2 subclasses and one of the class Hero's subclass is Crusader.

class Unit {
  template <typename target>
  virtual void  Attack(typename std::vector<target>::iterator targetPtr, std::shared_ptr<AttackSkill> skillPtr) {} 
  //ERROR (template function can not be virtual)
};

class Hero : public Unit {
  template <typename target>
  virtual void  Attack(typename std::vector<target>::iterator targetPtr, std::shared_ptr<AttackSkill> skillPtr) {}
  // ERROR (template function can not be virtual)
};

class Crusader : public Hero {
  template <typename target>
  void Attack(std::vector<target>::iterator targetPtr, std::shared_ptr<AttackSkill> skillPtr) {}
};


// Unit vector that contains heroes and monsters 
std::vector<Unit> unitVector;

Crusader crusader1; 
unitVector.at(0).emplace_back[crusader1];

The problem is, i want to access Attack function from unitVector[0] by writing virtual void Attack functions to Hero class and Unit class but unfortunately the C++ does not allow that. What should I do to write these virtual functions? Or can i use some generic pointers (if there is a such thing) instead of templates?

I want to use the attack function from different monsters and different heroes, so i have to use templates. If i try to write different functions such as attackHero and attackMonster one of them doesn't work since when i do that i'm basically trying to take an iterator of some non-existent class's vector.

Please help me out!

Upvotes: 1

Views: 120

Answers (2)

Alex
Alex

Reputation: 1924

Templates are not the tool you need here. Polymorphism is.

Templates are useful for when you have similar or identical code that has to work on multiple unrelated types. Polymorphism is useful for when you have a set of directly related types that all need to do different things.

Here, you have Crusaders, Heros, and Monsters, all of which attack and take damage and move in different ways, but are also all different Units, meaning they're all directly related, and fall into the second category.

Now, theory is all well and good, but we're concerned more with the implementation rather than the theory...

To solve the problem, there's two fixes you need to do.

First, the class definitions are not correct as written - we don't need any of the template stuff so we can get rid of all of that.

class Unit
{
public:
    Unit();
    virtual ~Unit();

    virtual void Attack(std::shared_ptr<Unit> Target, const AttackSkill& skill);

};

...

class Hero : public Unit
{
public:
    Hero();
    virtual ~Hero();

    virtual void Attack(std::shared_ptr<Unit> Target, const AttackSkill& Skill) override;

};

...

class Crusader : public Hero
{
public:
    Crusader();
    ~Crusader();

    void Attack(std::shared_ptr<Unit> Target, const AttackSkill& Skill);

};

Here, I'm assuming Crusader does not have any subclasses - if it does, make sure the destructor and Attack() are both virtual.

Now, the second major change is your collection and insertion methods:


std::vector<std::shared_ptr<Unit>> Units;

...

Units.emplace_back(std::make_shared(new Crusader());

The main difference here is the use of pointers. Polymorphism in C++ basically requires that you use pointers - using plain objects without pointers will cause the compiler to "slice" your class, removing any of your custom behavior. This becomes a major problem if, say, you add a holiness modifier as a member of the Crusader class and then try to access that from a "sliced" class - the program will immediately crash because "slicing" removes anything not in the base class from the object.

To make them a bit "nicer" to use, I've wrapped the pointers here in std::shared_ptr. This provides automatic reference counting and destruction when there are no more references. However, the same rules apply whether you're using std::shared_ptr or raw pointers.

Upvotes: 1

Kerek
Kerek

Reputation: 1110

Your code should look like:

class IUnit
{
public:
    virtual void attack(IUnit& target, const AttackSkill& skill) = 0;
};

class Hero : public IUnit
{
public:
    virtual void attack(IUnit& target, const AttackSkill& skill) override { /* do something */ }
};

class Crusader final : public Hero 
{
public:
    void attack(IUnit& target, const AttackSkill& skill) final { /* do something else */}
};

What happens is that function templates are determined in compile time (static polymorphism), and we use virtual functions in order to determine which function should be called on run-time (dynamic polymorphism), thus, it is not possible to combine both. You should read this question for more information

Here, you want to create an attack(target, skill) function that uses the skill provided, according to the attacking unit (usually, an interface is denoted by I), and being given the attack skill used. It can operate on any type of IUnit, and each IUnit should implement it.

Let's assume that we implemented the class monster, we can use the attack as follows:

class Monster : public IUnit
{
public:
    virtual void attack(IUnit& target, const AttackSkill& skill) override { /* placeholder */ }
};


int main()
{
    Monster some_monster{};
    Crusader some_crusader{};

    some_crusader.attack(some_monster, AttackSkill{});
}

Few little remarks:

  1. Use the keyword override to tell the compile to check that it is indeed overriding the virtual function properly, it can save you a lot of debugging!
  2. Use the keyword final to tell the compiler that no other class should inherit from the class Crusader (you can do so for a specific function by replacing the override with final).

Upvotes: 1

Related Questions