Class Inheritance 2

"C++: an octopus made by nailing extra legs onto a dog." - Steve Taylor

Virtual Methods and Polymorphism

The specification (Employee.h) for an Employee class:
#ifndef EMPLOYEE_H
#define EMPLOYEE_H

#include <string>

class Employee           
{
  public:             
    Employee(const std::string& first, const std::string& last, float sal, int yrs);
    void setName(const std::string& first, const std::string& last);
    void setSalary(float newSalary);
    void setYears(int numYears);
    void Display() const;

  private:               
    std::string firstName_;  
    std::string lastName_;   
    float salary_;    
    int years_;       
};
#endif
The specification (Manager.h) for the Manager class:
#ifndef MANAGER_H
#define MANAGER_H
#include "Employee.h"

class Manager : public Employee
{
  public:
    Manager(const std::string& first, const std::string& last, float sal, int yrs, int dept, int emps);
    void setDeptNumber(int dept);
    void setNumEmployees(int emps);
    void Display() const;
    
  private:
    int deptNumber_;    // department managed
    int numEmployees_;  // employees in department
};
#endif
Does the following code compile as is? If not, make the necessary changes so it will compile then trace the execution of the program. What is the output? Why?

#include "Employee.h"
#include "Manager.h"
#include <iostream>

void func1(const Employee& emp)
{
  emp.Display();
  std::cout << std::endl;
}

void func2(const Manager& mgr)
{
  mgr.Display();
  std::cout << std::endl;
}

int main()
{
  Employee emp1("John", "Doe", 30000, 2);
  Manager mgr1("Mary", "Smith", 50000, 10, 5, 8); 

  func1(emp1);  // pass an Employee object
  func2(mgr1);  // pass a Manager object

  func1(mgr1);  // pass a Manager object
  func2(emp1);  // pass an Employee object
  return 0;
}
Output:
  Name: Doe, John
Salary: $30000.00
 Years: 2

  Name: Smith, Mary
Salary: $50000.00
 Years: 10
  Dept: 5
  Emps: 8

  Name: Smith, Mary
Salary: $50000.00
 Years: 10


This is what the compiler says:

In function 'int main1()':
 error: invalid initialization of reference of type 'const Manager&'' from expression of type 'Employee'
   func2(emp1);  // pass an Employee object
             ^
note: in passing argument 1 of 'void func2(const Manager&)'
 void func2(const Manager& mgr)
      ^
The following code won't compile. Remove the offending line(s) and then trace the execution of the program. What is the output? Why?

#include "Employee.h"
#include "Manager.h"
#include <iostream>

int main()
{
  Employee emp1("John", "Doe", 30000, 2);
  Manager mgr1("Mary", "Smith", 50000, 10, 5, 8); 

  Employee* empPtr1 = &emp1;
  Manager* mgrPtr1 = &mgr1;

  empPtr1->Display();
  std::cout << std::endl;

  mgrPtr1->Display();
  std::cout << std::endl;

    // OK, a Manager is-an Employee
  empPtr1 = &mgr1;

  empPtr1->setYears(11);
  empPtr1->setNumEmployees(12);
  empPtr1->Display();
  std::cout << std::endl;
  return 0;
}
Output:
  Name: Doe, John
Salary: $30000.00
 Years: 2

  Name: Smith, Mary
Salary: $50000.00
 Years: 10
  Dept: 5
  Emps: 8

  Name: Smith, Mary
Salary: $50000.00
 Years: 11


The compiler complains:

In function 'int main2()':
error: 'class Employee' has no member named 'setNumEmployees'
   empPtr1->setNumEmployees(12);
            ^
What is the result of adding the following to main: (Compile? Run?)

static_cast<Manager *>(empPtr1)->setNumEmployees(12); // ???
static_cast<Manager *>(empPtr1)->Display();           // ???
How about this code: (Compile? Run?)

static_cast<Manager *>(&emp1)->Display(); // ???
How about this code: (Compile? Run?)

static_cast<Manager *>(&emp1)->setNumEmployees(10); // ???


This program creates an array of pointers to Employee objects. It displays each object using a for loop. Make sure you understand what the program is trying to do.

#include "Employee.h"
#include "Manager.h"
#include <iostream>

int main()
{
    // Create the personnel
  Employee emp1("John", "Doe", 30000, 2);
  Employee emp2("Nigel", "Tufnel", 35000, 4);
  Manager mgr1("Mary", "Smith", 50000, 10, 5, 8); 
  Manager mgr2("Derek", "Smalls", 60000, 13, 6, 5); 

    // Create an array to hold pointers to the 4 objects
  Employee* personnel[4];

    // Assign a pointer for each object
  personnel[0] = &emp1; // OK, an Employee
  personnel[1] = &emp2; // OK, an Employee
  personnel[2] = &mgr1; // OK, a Manager is an Employee
  personnel[3] = &mgr2; // OK, a Manager is an Employee

    // Loop through and display each object
  for (int i = 0; i < 4; i++)
  {
    personnel[i]->Display();
    std::cout << std::endl;
  }
  return 0;
}
Output:
  Name: Doe, John
Salary: $30000.00
 Years: 2

  Name: Tufnel, Nigel
Salary: $35000.00
 Years: 4

  Name: Smith, Mary
Salary: $50000.00
 Years: 10

  Name: Smalls, Derek
Salary: $60000.00
 Years: 13

What we really wanted was to have each object display all of its data. The Employee objects displayed all of their data, but the Manager objects only displayed the data that they have in common with an Employee. We really wanted this to display:

  Name: Doe, John
Salary: $30000.00
 Years: 2



  Name: Tufnel, Nigel
Salary: $35000.00
 Years: 4
  Name: Smith, Mary
Salary: $50000.00
 Years: 10
  Dept: 5
  Emps: 8

  Name: Smalls, Derek
Salary: $60000.00
 Years: 13
  Dept: 6
  Emps: 5
Because the personnel[] array is an array of pointers to Employee objects, when the compiler sees the statement:

   personnel[i]->Display();

This should bring up these points:

The How is pretty simple: Why isn't dynamic binding the default? There are a couple of reasons: So, in a nutshell, what is a virtual method? A virtual method allows a derived class to replace the implementation that was provided by the base class.

A Closer Look at Static Binding vs. Dynamic Binding (Polymorphism)

On the surface, the concept of virtual methods seems strange, complex, or even magical. In fact, its all three. To understand dynamic binding of methods (virtual methods) you must first understand static binding. Notes about virtual methods: Additional notes:

A very oversimplified example:
Without virtual methods: (i.e. Employee::Display() is NOT marked as virtual)
int main()
{
    // Create an Employee and a Manager
  Employee emp("John", "Doe", 30000, 2);
  Manager mgr("Mary", "Smith", 50000, 10, 5, 8); 

    // Display them
  emp.Display(); // Compiler/linker generated JUMP to address 2316
  mgr.Display(); // Compiler/linker generated JUMP to address 1300
  
  Employee *pe = &emp; // OK
  Employee *pm = &mgr; // OK, a Manager is an Employee
  
    // Display them
  pe->Display(); // Compiler/linker generated JUMP to address 2316
  pm->Display(); // Compiler/linker generated JUMP to address 2316 (pm is an Employee pointer)
  return 0;
}
With virtual methods: (i.e. Employee::Display() is marked as virtual)
int main()
{
    // Create an Employee and a Manager
  Employee emp("John", "Doe", 30000, 2);
  Manager mgr("Mary", "Smith", 50000, 10, 5, 8); 

    // Display them
  emp.Display(); // Compiler/linker generated JUMP to address 2316
  mgr.Display(); // Compiler/linker generated JUMP to address 1300
  
  Employee *pe = &emp; // OK
  Employee *pm = &mgr; // OK, a Manager is an Employee
  
    // Display them
  pe->Display(); // Compiler generated code to perform lookup at runtime.
                 //   Finds Display() at address 2316
  pm->Display(); // Compiler generated code to perform lookup at runtime.
                 // Finds Display() at address 1300
  return 0;
}

Virtual Function Tips

Virtual Method Tables Special Functions


Base Classes

We know that all squares are rectangles, but all rectangles are not squares. Sounds like a perfect example of an "is-a" relationship (read: inheritance).

So, we sketch out the interface to our base class Rectangle:

class Rectangle
{
  public:
      // Constructor (default)
    Rectangle(double x = 0, double y = 0, double length = 0, double width = 0);

      // Rectangle-specific get/set methods
    double getLength() const;
    double getWidth() const;
    void setLength(double);
    void setWidth(double);
    double getCenterX() const;
    double getCenterY() const;
    void SetCenter(double x, double y);

      // Need to be redefined in derived classes
    virtual double Area() const;
    virtual void Draw() const;
    virtual void Scale(double scale);

  private:
    double center_x_; // x-coordinate of center point
    double center_y_; // y-coordinate of center point
    double length_;   // "long" sides
    double width_;    // "short" sides
};
Once we've got that scoped out, we can work on the Square class:
class Square : public Rectangle
{
  public:
      // Constructor
    Square(double x, double y, double side);

      // Square-specific Get methods
    double GetSide() const;
    void SetSide(double side);

      // Methods from Rectangle that we need to specialize
    virtual double Area() const;
    virtual void Draw() const;
    virtual void Scale(double scale);

  private:
    double side_;  // length of a side
};
But, something's not right. This is what the Square class sort of looks like now (artist's rendering). There are several problems: Even though in the "real world" this relationship seems straight-forward, we need to re-think the design. It would be simpler to just create the Square class "from scratch":

class Square
{
  public:
      // Constructor
    Square(double x, double y, double side);

    double getCenterX() const;
    double getCenterY() const;
    void SetCenter(double x, double y);
    double GetSide() const;
    void SetSide(double side);

    double Area() const;
    void Draw() const;
    void Scale(double scale);

  private:
    double center_x_; // x-coordinate of center point
    double center_y_; // y-coordinate of center point
    double side_;     // length of a side
};
We've traded one set of "problems" for another.
class Figure
{
  public:
      // Constructor
    Figure(double x = 0, double y = 0);

      // get/set
    double getCenterX() const;
    double getCenterY() const;
    void SetCenter(double x, double y);

      // Virtual methods common to both
    virtual double Area() const;
    virtual void Draw() const;
    virtual void Scale(double scale);

  private:
    double center_x_; // x-coordinate of center point
    double center_y_; // y-coordinate of center point
};
Rectangle and Square are now derived from Figure:

#include "Figure.h"

class Rectangle : public Figure
{
  public:
      // Constructor (default)
    Rectangle(double x = 0, 
              double y = 0, 
              double length = 0, 
              double width = 0);

      // Rectangle-specific get/set methods
    double getLength() const;
    double getWidth() const;
    void setLength();
    void setWidth();

      // Methods from Figure that
      // we need to specialize
    virtual double Area() const;
    virtual void Draw() const;
    virtual void Scale(double scale);

  private:
    double length_; // "long" sides
    double width_;  // "short" sides
};
#include "Figure.h"

class Square : public Figure
{
  public:
      // Constructor (default)
    Square(double x = 0, 
           double y = 0, 
           double side = 0);


      // Square-specific get/set methods
    double GetSide() const;
    void SetSide(double side);



      // Methods from Figure that
      // we need to specialize
    virtual double Area() const;
    virtual void Draw() const;
    virtual void Scale(double scale);

  private:
    double side_; // length of a side
};

Sample client code:
Rectangle r(4, 5, 10, 3);
Square s(2, 3, 6);
Figure *figs[2] = {&r, &s};

for (int i = 0; i < 2; i++)
{
  figs[i]->Draw();
  cout << "Area: " << figs[i]->Area() << endl;
}
Output:
Drawing the rectangle: 10x3
Area: 30
Drawing the square: 6
Area: 36
Here are the simplistic Draw methods:

void Rectangle::Draw() const
{
  cout << "Drawing the rectangle: " << length_ << "x" << width_ << endl;
}

void Square::Draw() const
{
  cout << "Drawing the square: " << side_ << endl;
}
Notes:

Abstract Base Classes

If I said: "Everyone take out a pencil and draw a figure centered at (0, 0) on an X-Y grid.", what would we see?

An (incomplete) example:

Base class Figure:

class Figure
{
  public:
    Figure(double x = 0, double y = 0);
    virtual void Draw() const;
    virtual double Area() const;
    virtual void Scale(double scale);

  private:
    double center_x_; // x-coord center
    double center_y_; // y-coord center
};
Derived classes Circle and Square:

class Circle : public Figure
{
  public:
    Circle(double x = 0, double y = 0, 
           double radius = 0);
    virtual double Area() const;
    virtual void Draw() const;
    virtual void Scale(double scale);

  private:
    double radius_; 
};
class Square : public Figure
{
  public:
    Square(double x = 0, double y = 0, 
           double side = 0);
    virtual double Area() const;
    virtual void Draw() const;
    virtual void Scale(double scale);

  private:
    double side_; // length of a side
};
Sample client code:

int main()
{
  Circle circle(0, 0, 5); // Circle at (0,0) with radius=5
  Square square(0, 0, 8); // Square at (0,0) with side=8
  Figure figure(3, 9);    // Figure at (3, 9)

  circle.Draw(); // draws the Circle
  square.Draw(); // draws the Square
  figure.Draw(); // ???
  return 0;
}
The implementation of the Figure class:

#include "Figure.h"

Figure::Figure(double x, double y)
{
  center_x_ = x;
  center_y_ = y;
}

double Figure::Area() const
{
  // What's the area of a Figure?
}

void Figure::Draw() const
{
  // How do you draw a Figure?
}

void Figure::Scale(double scale)
{
  // How do you scale a Figure?
}
There's obviously a problem with implementing some methods of the Figure:

Notes on abstract classes:
Figure is now an abstract base class

class Figure
{
  public:
    Figure(double x = 0, double y = 0);
    virtual void Draw() const = 0;        // pure virtual
    virtual double Area() const = 0;      // pure virtual
    virtual void Scale(double scale) = 0; // pure virtual

  private:
    double center_x_; // x-coord center
    double center_y_; // y-coord center
};
Client code:
int main()
{
  Circle circle(0, 0, 5); // Circle at (0,0) with radius=5
  Square square(0, 0, 8); // Square at (0,0) with side=8
  Figure figure(3, 9);    // Compile error
  ...
}
Error message from the GNU C++ compiler:

cannot declare variable `figure' to be of type `Figure' because 
the following virtual functions are abstract:
  virtual double Figure::Area() const
  virtual void Figure::Draw() const
  virtual void Figure::Scale(double)

Designing a class heirarchy is an advanced skill usually performed by experienced programmers. Since a design may need to last for years, it is important to get it right the first time. This is why experience helps.

Another Example of Polymorphism

We want to create a game where different players have different powers and strengths. This is our base class, Player:
#ifndef PLAYER_H
#define PLAYER_H

#include <string>

class Player
{
  public:
    Player(const std::string& name, int health, int damage);
    virtual ~Player();

    virtual std::string WhoAmI() const = 0;
    virtual void Attack(Player &other) const;      // Primary attack (punch)
    virtual void Attack2(Player &other) const = 0; // Secondary attack (varies)
    void TakeDamage(int damage);

    const std::string& getName() const;
    int getHealth() const;
    int getDamage() const;
    bool isAlive() const;

  private:
    std::string name_; // The player's name
    int health_;       // The player's health level
    int damage_;       // How much damage the player can inflict
};
#endif // PLAYER_H
Implementation:
#include <string>
#include "Player.h"

using std::string;

Player::Player(const string& name, int health, int damage) 
               : name_(name), health_(health), damage_(damage)
{
}

Player::~Player()
{
  // intentionally left empty 
}
int Player::getHealth() const
{
  return health_;
}
const string& Player::getName() const
{
  return name_;
}
int Player::getDamage() const
{
  return damage_;
}
bool Player::isAlive() const
{
  return health_ > 0;
}
void Player::Attack(Player &player) const
{
  player.TakeDamage(damage_);
}
void Player::TakeDamage(int damage)
{
  health_ -= damage;
}
We need to derive 3 classes from our base class. We'll call them Scout, Soldier, and Pyro. Scout and Soldier will be derived from Player, and Pyro will be derived from Soldier.

All 3 classes have the ability to attack (i.e. punch) another player, with varying degrees of damage. Each class will also have a secondary attack mode, which can be very different from each other.

Scout

#include "Player.h"
#include <sstream>

class Scout : public Player
{
  public:
    Scout(const std::string &name, int health = 50, int punch_damage = 1, 
          int damage2 = 5) : Player(name, health, punch_damage), damage2_(damage2)
    {
    }

    virtual ~Scout() {};

    std::string WhoAmI() const
    {
      std::stringstream ss;
      ss << "I'm a Scout named " << getName() 
         << " [" << getHealth() << "," << getDamage() << "," << damage2_ << "]";

      return ss.str();
    }

    void Attack2(Player &player) const
    {
      player.TakeDamage(damage2_);
    }

  private:
    int damage2_;
};
Soldier
#ifndef SOLDIER_H
#define SOLDIER_H

#include "Player.h"
#include <sstream>

class Soldier : public Player
{
  public:
    Soldier(const std::string &name, int health = 75, int punch_damage = 2, 
            int damage2 = 3) : Player(name, health, punch_damage), damage2_(damage2)
    {
    }

    virtual ~Soldier() {};

    std::string WhoAmI() const
    {
      std::stringstream ss;
      ss << "I'm a Soldier named " << getName() 
         << " [" << getHealth() << "," << getDamage() << "," << damage2_ << "]";

      return ss.str();
    }

    void Attack2(Player &player) const
    {
      player.TakeDamage(damage2_);
    }
    
  private:
    int damage2_;
};
#endif
Pyro
#include "Soldier.h"
#include <sstream>

class Pyro : public Soldier
{
  public:
    Pyro(const std::string &name, int health = 100, int punch_damage = 3, 
         int damage2 = 2) : Soldier(name, health, punch_damage), damage2_(damage2)
    {
    }

    virtual ~Pyro() {};

    std::string WhoAmI() const
    {
      std::stringstream ss;
      ss << "I'm a Pyro named " << getName() 
         << " [" << getHealth() << "," << getDamage() << "," << damage2_ << "]";

      return ss.str();
    }

    void Attack2(Player &player) const
    {
      player.TakeDamage(damage2_);
    }
    
  private:
    int damage2_;
};
Doxygen output

GUI Hierarchy

This code:

  // No polymorphism
Scout scout("Moe", 100, 1, 5);
Soldier soldier("Larry", 150, 2, 10);
Pyro pyro("Curly", 200, 3, 15);

cout << scout.WhoAmI() << endl;
cout << soldier.WhoAmI() << endl;
cout << pyro.WhoAmI() << endl;
produces this output:
I'm a Scout named Moe [100,1,5]
I'm a Soldier named Larry [150,2,10]
I'm a Pyro named Curly [200,3,15]
And this code:
  // Take the default values
Player *p[] = {new Scout("Moe"), new Soldier("Larry"), new Pyro("Curly")};

  // Polymorphism
for (unsigned i = 0; i < sizeof(p) / sizeof(*p); i++)
{
  cout << p[i]->WhoAmI() << endl;
  delete p[i];
}
produces similar output:
I'm a Scout named Moe [50,1,5]
I'm a Soldier named Larry [75,2,3]
I'm a Pyro named Curly [100,3,2]
Now, we can make two teams of Players and have them fight it out:
void TestFight()
{
    // Red team
  vector<Player *> red_team;
  red_team.push_back(new Scout("Moe", 20, 1, 5));
  red_team.push_back(new Soldier("Larry", 30, 2, 3));
  red_team.push_back(new Pyro("Curly", 40, 3, 2));

    // Blue team
  vector<Player *> blue_team;
  blue_team.push_back(new Scout("Fred", 20, 1, 5));
  blue_team.push_back(new Soldier("Barney", 30, 2, 3));
  blue_team.push_back(new Pyro("Wilma", 40, 3, 2));

  fight(red_team, blue_team);
  print_results(red_team, blue_team);

  delete_team(red_team);
  delete_team(blue_team);
}
Output after 3 runs:
Team 1:
I'm a Scout named Moe [20,1,5]
I'm a Soldier named Larry [30,2,3]
I'm a Pyro named Curly [40,3,2]
Team 2:
I'm a Scout named Fred [20,1,5]
I'm a Soldier named Barney [30,2,3]
I'm a Pyro named Wilma [40,3,2]

Fred was killed by Larry
Wilma was killed by Larry
Barney was killed by Moe
Team 1 wins!
Team 1: Moe[6]  Larry[12]  Curly[35]  
Team 2: Fred[-1]  Barney[-3]  Wilma[-1]  
Team 1:
I'm a Scout named Moe [20,1,5]
I'm a Soldier named Larry [30,2,3]
I'm a Pyro named Curly [40,3,2]
Team 2:
I'm a Scout named Fred [20,1,5]
I'm a Soldier named Barney [30,2,3]
I'm a Pyro named Wilma [40,3,2]

Moe was killed by Fred
Fred was killed by Curly
Barney was killed by Curly
Larry was killed by Wilma
Curly was killed by Wilma
Team 2 wins!
Team 1: Moe[-2]  Larry[-2]  Curly[-1]  
Team 2: Fred[0]  Barney[-2]  Wilma[4]  
Team 1:
I'm a Scout named Moe [20,1,5]
I'm a Soldier named Larry [30,2,3]
I'm a Pyro named Curly [40,3,2]
Team 2:
I'm a Scout named Fred [20,1,5]
I'm a Soldier named Barney [30,2,3]
I'm a Pyro named Wilma [40,3,2]

Fred was killed by Moe
Moe was killed by Wilma
Larry was killed by Wilma
Curly was killed by Wilma
Team 2 wins!
Team 1: Moe[-1]  Larry[-1]  Curly[0]  
Team 2: Fred[0]  Barney[10]  Wilma[19]  
Full driver.cpp

verbose output

stress output with 100 random players on each team