Overview
This lab takes the Character, Enemy, and Hero classes and turns a shared virtual info() method into working runtime polymorphism. You will implement Character's default info() body, override it in Enemy and Hero with role-specific messages, and then build a std::vector<Character*> that holds both a Hero and an Enemy so that a single loop calling ->info() on each element still prints the correct override for the object it actually points to.
Learning Objectives
By the end of this lab you will be able to:
- Declare and implement a virtual method on a base class so it has a default body derived classes can replace
- Override a virtual method in two sibling derived classes with distinct behaviour
- Store pointers to different derived types together in a single std::vector
- Explain why a virtual method called through a base-class pointer runs the derived override rather than the base version
Concept
The Inheritance lab showed that Car could hide Vehicle's start() only when called through a Car-typed variable, because a non-virtual method is chosen entirely by the compile-time type of the pointer or reference used to call it. That breaks down as soon as several different objects need to be treated the same way. A loop that processes every combatant in a single collection cannot hold a std::vector<Hero> and a std::vector<Enemy> at the same time and still run one shared loop over both, because Hero and Enemy are different types.
Polymorphism solves this by letting a call through a common base type run whichever derived version actually matches the object underneath. Character declares info() as virtual. Marking a method virtual tells the compiler to defer the choice of which function body runs until the program is executing, looking it up against the object's actual type rather than deciding from the declared type of the pointer used to call it. Enemy and Hero each override info() with their own version of the same signature, marked override so the compiler checks it really does match a virtual method in the base.
Once info() is virtual, a std::vector<Character*> can hold pointers to both Hero and Enemy objects side by side. Calling ->info() on each element in a single loop still prints the Hero's message for the Hero and the Enemy's message for the Enemy, even though every pointer in the vector has the same declared type, Character*.
Tasks
Task 1 - The Character base class
Open Character.h and confirm info() is already declared virtual. Then open Character.cpp and complete Character::info().
- In
Character::info(), print the character'sname,health, andstrengthtostd::cout.
Success condition: constructing a Character directly and calling info() prints its name, health, and strength.
#pragma once
#include <string>
#include <iostream>
class Character
{
private:
std::string name;
int health;
int strength;
public:
Character(std::string name, int health, int strength);
~Character();
std::string getName();
int getHealth();
int getStrength();
void setName(std::string name);
void setHealth(int h);
void setStrength(int s);
virtual void info();
void takeDamage(int damage);
};
#include "Character.h"
Character::Character(std::string name, int health, int strength)
{
this->name = name;
this->health = health;
this->strength = strength;
}
Character::~Character() {}
std::string Character::getName()
{
return name;
}
int Character::getHealth()
{
return health;
}
int Character::getStrength()
{
return strength;
}
void Character::setName(std::string name)
{
this->name = name;
}
void Character::setHealth(int h)
{
health = h;
}
void Character::setStrength(int s)
{
strength = s;
}
void Character::info()
{
// TODO: print this character's name, health, and strength to std::cout
}
void Character::takeDamage(int damage)
{
health = health - damage;
}
Task 2 - Enemy and Hero override info()
Open Enemy.cpp and Hero.cpp and complete each class's info() override. Both classes already declare void info() override; in their headers below, which is what makes each definition count as a genuine override of Character::info() rather than an unrelated new method.
- In
Enemy::info(), print that this character is an enemy, along with its name, health, and strength. - In
Hero::info(), print that this character is a hero, along with its name, health, and strength.
Success condition: constructing an Enemy and calling info() prints an enemy-specific message, and constructing a Hero and calling info() prints a hero-specific message.
#pragma once
#include "Character.h"
class Enemy : public Character
{
public:
Enemy(std::string name, int health, int strength);
~Enemy() {}
void info() override;
};
#include "Enemy.h"
Enemy::Enemy(std::string name, int health, int strength) : Character(name, health, strength) {}
void Enemy::info()
{
// TODO: print that this is an enemy, along with its name, health, and strength
}
#pragma once
#include "Character.h"
class Hero : public Character
{
public:
Hero(std::string name, int health, int strength);
~Hero() {}
void info() override;
};
#include "Hero.h"
Hero::Hero(std::string name, int health, int strength) : Character(name, health, strength) {}
void Hero::info()
{
// TODO: print that this is a hero, along with its name, health, and strength
}
Task 3 - Runtime dispatch through Character* pointers
Open main.cpp. dave (a Hero) and teifion (an Enemy) are already constructed. Store both of them behind pointers of the same declared type and call info() on each through that shared type.
- Declare a
std::vector<Character*>namedparty. push_backthe address ofdave, then the address ofteifion, intoparty.- Loop over
partywith a range-basedforand call->info()on each element.
Success condition: running the program prints dave's hero message followed by teifion's enemy message, even though every element of party has the same declared type, Character*.
#include <iostream>
#include <vector>
#include "Hero.h"
#include "Enemy.h"
int main()
{
Hero dave("Dave", 100, 10);
Enemy teifion("Teifion", 40, 15);
// TODO: declare a std::vector<Character*> named party
// TODO: push_back the address of dave, then the address of teifion, into party
// TODO: loop over party with a range-based for and call ->info() on each element
return 0;
}
The diagram below shows what happens at each call to ->info() inside that loop. The pointer's declared type never changes, but the function body that actually runs depends on the object it points to.
Solution
Try the tasks above before revealing the solution.