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.
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 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.
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.
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.
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:
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)
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.
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;