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;

  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;
  personnel[1] = &emp2;
  personnel[2] = &mgr1;  // a Manager is an Employee
  personnel[3] = &mgr2;  // 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's 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
{
  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

  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_x, double scale_y);
};
Once we've got that scoped out, we can work on the Square class:
class Square : public Rectangle
{
  private:
    double side_;  // length of a side

  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);
};
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
{
  private:
    double center_x_; // x-coordinate of center point
    double center_y_; // y-coordinate of center point
    double side_;     // length of a side

  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);
};
We've traded one set of "problems" for another.
class Figure
{
  private:
    double center_x_; // x-coordinate of center point
    double center_y_; // y-coordinate of center point

  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;
};
Rectangle and Square are now derived from Figure:

#include "Figure.h"

class Rectangle : public Figure
{
  private:
    double length_; // "long" sides
    double width_;  // "short" sides

  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();
    void Scale(double scale_x, double scale_y);

      // Methods from Figure that
      // we need to specialize
    virtual double Area() const;
    virtual void Draw() const;
};
#include "Figure.h"

class Square : public Figure
{
  private:
    double side_; // length of a side


  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);
    void Scale(double scale);



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

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
{
  private:
    double center_x_; // x-coord center
    double center_y_; // y-coord center

  public:
    Figure(double x = 0, double y = 0);
    virtual void Draw() const;
    virtual double Area() const;
};
Derived classes Circle and Square:

class Circle : public Figure
{
  private:
    double radius_; 

  public:
    Circle(double x = 0, double y = 0, 
           double radius = 0);
    virtual double Area() const;
    virtual void Draw() const;
};
class Square : public Figure
{
  private:
    double side_; // length of a side

  public:
    Square(double x = 0, double y = 0, 
           double side = 0);
    virtual double Area() const;
    virtual void Draw() const;
};
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?
}
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
{
  private:
    double center_x_; // x-coord center
    double center_y_; // y-coord center

  public:
    Figure(double x = 0, double y = 0);
    virtual void Draw() const = 0;   // pure virtual
    virtual double Area() const = 0; // pure virtual
};
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