CSC262 Feb15

slide version

single file version

Contents

  1. A template Array Class
  2. 1. Using class types to construct new classes.
  3. 1.1 Composition
  4. 1.1.1 Example 1: Stack
  5. Stack (cont)
  6. 1.1.2 Example 2: Student Record
  7. 1.2 Inheritance
  8. 1.2.1 Example 1: Employee, Manager (getBonus, getLevel)
  9. 1.2.1a What does "derived type is a kind of base type" mean?
  10. 1.2.1b So what does this mean?
  11. Result
  12. 1.2.2 Accessing base class data in the derived class.
  13. Alternative
  14. 1.2.3 Constructors
  15. 1.2.4 Destructors
  16. 1.2.4 Virtual Functions
  17. The main Function
  18. 1.2.5 Dynamic Dispatch, Overriding, and Overloading
  19. 1.2.5 Pointer types versus Value types with Virtual Functions

A template Array Class[1] [top]

We will create a class that acts like an array, but which grows automatically as needed and the type of elements in an Array can be specified in the declaration.

1. Using class types to construct new classes.[2] [top]

There are several ways that existing classes can be used to create new ones, thus promoting reuse of tested code you have written.

The two primary ways of building new classes from existing classes:

  1. composition
  2. inheritance

1.1 Composition[3] [top]

This just means creating a class with data members that are also of class type.

The new class is "composed" or "has" data members of another class.

1.1.1 Example 1: Stack[4] [top]

We can use the Array class to create a Stack class.

template <typename T>
class Stack
{
private:
  Array<T> a; // Using the Array class
public:
  Stack();
  bool empty() const;
  void push(const T& x);
  T pop();
  T top() const;
};

This Stack class "has" an Array member.

Stack (cont)[5] [top]

A Stack is NOT an Array. The operations allowed on a Array are not allowed on a Stack.

Why do this? Because it makes it easy to implement the new Stack class. For example,

template <typename T>
Stack<T>::Stack() {}

template <typename T>
bool Stack<T>::empty() const
{
  return a.size() == 0;
}

template <typename T>
void Stack<T>::push(int x) 
{
  a.add(x);
}

template <typename T>
int Stack<T>::pop()
{
  int n;
  assert(a.size() != 0 );

  n = a.remove(a.size() - 1);

  return n;
}

1.1.2 Example 2: Student Record [6] [top]

A student record has a name, id, and list of test scores.

class StudentRecord
{
private:
  string student_name; // Note string is a (standard c++) class
  string student_id;   
  Array<int> scores;
public:
  StudentRecord(string nm, string id)
	: student_name(nm), student_id(id) {}
  int getNumScores() const;
  double getAvg() const;
  int getMaxScore() const;
  int getMinScore() const;
  Array getScores() const;

  void addTestScore(int value);
};	

1.2 Inheritance[7] [top]

This is a more complicated way of using an existing class to help create a new one.

The new class declares that it "inherits" from the existing class and automatically gets all the data and function members.

The definition of the new class does NOT repeat these inherited declaraions.

The new class CAN add additional data and/or function members.

The class type defined by the new class is compatible with the existing type. It is just more specialized since it may have extra data and function members.

The new type is said to be a "kind of" the type it inherits from.

The new class is called the "derived" class. The existing class is called a "base" class.

1.2.1 Example 1: Employee, Manager (getBonus, getLevel)[8] [top]

class Employee
{
private:
  string name;
  double salary;
public:
  Employee(const string& nm, double sal) 
	: name(nm), salary(sal) {}
  string getName() const { return name; }
  double getSalary() const { return salary; }
  virtual double getBonus() const;
};

A Manager is particular "kind of" Employee.

Define Manager class as a derived class from the base class Employee:

class Manager : public Employee
{
private:
  int level; // Something special for Managers, not regular Employees
public:
  Manager(const string& nm, double sal, int lev)
	: Employee(nm, sal), level(lev) 
  {}

  virtual double getBonus() const;
  int getLevel() const { return level; }
  void setLevel(int lev) { level = lev; }

};

1.2.1a What does "derived type is a kind of base type" mean?[9] [top]


Employee e1("Bob", 45000.00);
Manager m1("Alex", 60000.00);

e1 = m1;  // Huh?

   e1                     m1           
   +-----------+          +-----------+
   | Emp vptr  |	  | Mgr vptr  | <== Not assigned to e1
   | name:  ...|    	  | name:  ...|
   | salary:...|     <--  | salary:...| <--  Employee part of Manager
   +-----------+	  +-----------+        (assigned to e1)
                          | level: ...| <== Not assigned to e1
                          +-----------+        (e1 has no 'level')
and

Employee *ep1 = new Employee("Bob", 45000.00);
Manager *mp1 = new Manager("Alex", 60000.00);

ep1 = mp1;

     ep1              mp1
     +---+            +---+     +-----------+
     | --|---+        | --|---->| Mgr vptr  |
     +---+   |        +---+   ^ | name:  ...|
             |                | | salary:...|
             +----------------+ +-----------+
      				| level: ...|
				+-----------+

1.2.1b So what does this mean?[10] [top]

Here are the two implementations of getBonus functions:



  virtual double getBonus() const
    {
      return 0.01 * salary;
    }



  virtual double getBonus() const
    {
      return 2 * getSalary();
    }

Result[11] [top]

The result:

int main()
{
  Employee e1("Bob", 45000.00);
  Manager m1("Alex", 60000.00);

  cout << e1.getName() " has bonus " 
	<< e1.getBonus() << endl;
  // Prints: Bob has bonus 450

  cout << m1.getName() " has bonus " 
	<< m1.getBonus() << endl;
  // Prints: Alex has bonus 120000
  

  e1 = m1;  

  cout << e1.getName() " has bonus " 
	<< e1.getBonus() << endl;
  // Prints: Alex has bonus 600
    

So although e1's name has been changed from Bob to Alex and e1's salary has been changed from 45000 to 60000 e1 still uses the virtual function table for Employee.

So e1's bonus is calculated at .01 * salary instead of 2 * salary.

1.2.2 Accessing base class data in the derived class.[12] [top]

The Manager class inherits both the public and private sections of the Employee class, but can only access them through the public interface.



double Manager::getBonus() const
{
 return 2 * salary;   // ERROR. salary is private in Employee
}



double Manager::getBonus() const
{
 return 2 * getSalary();   // getSalary is public in Employee
}

Alternative[13] [top]

An alternative would be to put the data members in a 'protected' section.

class Employee
{
protected:  // Derived classes can access protected members
  string name;     // For other code, protected is like private
  double salary;
public:
  Employee(const string& nm, double sal) 
	: name(nm), salary(sal) {}
  string getName() const { return name; }
  double getSalary() const { return salary; }
  virtual double getBonus() const;
};

1.2.3 Constructors[14] [top]

In the initialization list for the Manager constructor, an Employee constructor can be passed parameters to initialize the Employee part of a Manager.

  Manager::Manager(const string& nm, double sal, int lev)
	: Employee(nm, sal), level(lev) 
  {}

The initialization of the base class occurs first, then the initialization of the derived class.

If no Employee constructor were listed in the initialization list, the Employee no argument constructor (if there is one) would be used to initialize the Employee part. If there were no effective no argument Employee constructor, an error occurs for the Manager constructor.

1.2.4 Destructors[15] [top]

This example doesn't have any destructors because there are no pointer members.

In general, however, the destructors execute in reverse order. First the derived class destructor, then the base class destructor.

1.2.4 Virtual Functions[16] [top]

The getBonus() function in the base class has the qualifier "virtual".

A table of the beginning addresses of the virtual functions for a class is created. The virtual function table.

There is one such table for each class. All values of a given class type use the same virtual function table; that is, a object value of a class type has stored implicitly in the object a pointer to the virtual function table for its class.

Calls to member functions which are virtual functions may be "looked up" in the virtual function table.

Employee and Manager have different virtual function tables.

So a call to the virtual function "getBonus" will be "looked up" in the Employee virtual function table or in the Manager virtual function table DEPENDING on whether the object being used is an Employee or a Manager.

Suppose these are the two implementations of getBonus:



  virtual double getBonus() const
    {
      return 0.01 * salary;
    }



  virtual double getBonus() const
    {
      return 2 * getSalary();
    }

The main Function[17] [top]

int main()
{
  Employee *p;
 
  p = new Employee("Bob", 45000.00);
  cout << p->getBonus() << endl;
  // Prints: 450  (using Employee's getBonus())

  p = new Manager("Alex", 60000.00);  // p now points to a Manager value

  cout << p->getBouns() << endl;
  // Prints: 120000  
  // (uses getBonus from Manager's virtual function table) 

1.2.5 Dynamic Dispatch, Overriding, and Overloading[18] [top]

The new implementation of getBonus() in Manager is NOT overloading since the two definitions occur in different "scopes". One is in the Employee class and the other is in the Manager class.

Overloading is when two functions of the SAME name are defined in the same scope. In this case they MUST have different parameter lists.

Overriding is when a function of the SAME name AND the SAME parameter list is defined in both a base class and a derived class.

In C++, the base class function must be 'virtual' for overriding. Otherwise, it will not be put in the virtual function table.

Dynamic Dispatch
 Employee *p;
 ...
 cout << p->getBonus() << endl;
    

Dynamic dispatch refers to the determination of which implementation of getBonus should be called. It is dynamically determined (at run time) depending on the object that p points to.

If p points to an Employee object, the virtual function table will point to the Employee table and Employee's getBonus will be used.

If p points to an Manager object, the virtual function table will point to the Manager table and Manager's getBonus will be used.

1.2.5 Pointer types versus Value types with Virtual Functions[19] [top]

Dynamic dispatch is only used when using pointers, not with class values:

  Employee e1("Bob", 45000.00);
  Manager m1("Alex", 60000.00);

 // No dynamic dispatch here. Employee's getBonus is used
  e1 = m1;
  cout << e1.getBonus() << endl;  

  Employee *ep1 = new Employee("Bob", 45000.00);
  Manager *mp1 = new Employee("Alex", 60000.00);

 // Dynamic dispatch here since (a) pointers are used
 // and (b) the getBonus function is 'virtual'.
 // So Manager's getBonus is used since ep1 points to
 // a Manager object
  ep1 = mp1;
  cout << ep1->getBonus() << endl;