Lecture 7: Inheritance and Polymorphism


Lecture 7: Inheritance and Polymorphism




Now we begin looking at the powerful facility C++ has for defining relationships and sharing code between objects.
Example:
employ1.cpp without VIRTUAL_FUNCTIONS defined.
First look at Employee class. Before going any farther though, we diverge from the topic at hand to consider a special C++ feature.
7.1 static declarator for class members
We saw this previously in the date example.
Notice the static qualification for the head and list() members of the Employee definition. This is new to C++. These class members are really just encapsulated global objects. They are global variables and non-member functions which the class declaration controls access to. public items are accessible by all code. private items are accessible only by implementation code.
static member functions can only operate directly on static data members. They get no instance of the object, and so have no unqualified access to non-static member functions or data. Also, there is no this pointer. It can access non-static members if it can obtain an instance of its type somehow. static should only appear in the declaration.
A static member function can be accessed via class (e.g. Employee::list()) or the usual way. static data members don't make instances bigger, and they have global linkage and must be defined explicitly outside the class. As with the static member function, no static qualifier should appear when the static data member is defined. Example:
Employee *Employee::m_head;
7.2 Inheritance
Three types of Employee: Manager, Clerk, Typist. They all build on the Employee class. Manager adds data for group managed. Clerk adds data for ten-key score. Typist adds data for wpm score.
Note the method for specifying the construction of base class in constructor. It nothing is specified, the compiler assumes a no-argument constructor should be used and will expect to find one. public inheritance is the only strictly object-oriented type of inheritance. public allows the relationship to be exploited by all code. What is there to exploit? Access to public interface of base class as long as derived class doesn't redefine it. Polymorphism. Usually pointers and references are used here. No need for unsafe pointer casts in many situations where C would require them.
private means only implementation code can use the relationship. Just including an instance of the class as a data member seems a more straight-forward way to go.
7.3 Polymorphism and virtual functions
virtual member functions are the provision for dynamic binding in C++. Allows function code used to fulfill a function invocation to depend on the dynamic type of the object instead of the type it is being manipulated as (static type). Unlike other member functions, this member function makes instances of object bigger, typically by the size of one pointer regardless of number of virtual member functions. This pointer points to a table of function pointers for the virtual functions of the class. See Section 7.5 for more information on this table.
Example:
employ1.cpp with VIRTUAL_FUNCTIONS defined.
Right now Employee::list() just prints out the generic Employee data for each Employee. If instead, we wanted the data printed out to depend on the dynamic type of the Employee (dynamic binding), we should change the declaration of print() in Employee to:
virtual void print(ostream &) const;
Just adding the virtual key word to the base class declaration does the trick. Now list() call results in complete data being printed out for every Employee. Notice there is more overhead associated with virtual function calls, typically the time needed for dereferencing two pointers. In the Manager's print function, we just want to print the generic Employee data. So we could change the call to print() call to:
group[i]->Employee::print(os); // don't need all information here
This call does not incur the virtual function call overhead.
"Pure" virtual functions allow definition of abstract classes (classes for which no object can be created). This allows objects to be build in stages. In these classes, a member function is declared virtual, but not defined. Basically it defines an interface with will be used by all derived classes.
Shape example: Shape.H, Shape.C, OsScreen.H, OsScreen.C, XScreen.H, XScreen.C and MyShape.C.
Sometimes the term polymorphism is used loosely to refer to the idea of parameterized types which is implemented in C++ with templates.
virtual functions can be used to get rid of switch statements. virtual functions are much easier to maintain. When a new derived class is defined, all that need be done is to define a new version of the virtual function instead of going through and updating umpteen switch statements, some of which might be inadvertently overlooked. Objects become "smarter". It knows how to process itself in each situation, so the receiving function is relieved of this reponsibility. This is where polymorphism really becomes useful for simplifying code.
7.4 virtual destructors
Another problem: what happens when we want to delete a heap object when the reference we wish to delete through refers to a different type than the one it was created with? Notice if a Manager is deleted through an Employee pointer, only the Employee part will be destroyed, and the memory for the Manager's Employee list will never get returned to the heap. The solution is a virtual destructor for the base class. All derived classes will have a destructor set up for them whether they define one or not. Destructor is now accessed via indirection. This should be done whenever at least one function in a class is declared virtual. Just by doing this, all destructors in inheriting classes become virtual (even though they don't share the same name as with the non-destructor virtual member functions).
virtual constructors? No. But if you want an object to be able to create a new instance of its type or a clone of itself when its creation type has been hidden via a cast, just create a virtual function which uses new to create an instance. When creating clones, usual precautions of duplicating pointers initialized with new must be taken.
7.5 The virtual table
The virtual function capability is typically implemented using what is called a "virtual table". When a member function of employee has been declared virtual, a memory image of an employee instance would appear as follows:
vtable *;  // actual type depends on compiler
char *;
long;
The memory image of an employee's virtual table (what vtable * points to) looks like:
void (*print)(); => Employee::print
void (*x)(int);  => Employee::x
long (*y)(int, double);  => Employee::y
where x() and y() are two other virtual functions of Employee which have been hypothetically added to Employee class declaration.
Memory image of Manager instance:
vtable *;
char *;
long;
Employee **;
int;
Memory image of Manager's virtual table looks like:
void (*print)(); => Manager::print
void (*x)(int);  => Manager::x
long (*y)(int, double);  => Employee::y  // Manager doesn't define this one
double (*a)();  => Manager::a    // two new virtual functions hypothetically
long (*b)(int);  =>  Manager::b  // added to declaration of Manager class
                                 // these don't appear in Employee, but
                                 // will be dynamically bound for
                                 // classes which inherit from Manager
7.6 Multiple inheritance
Sometimes its useful to combine classes horizontally. Create a menu of classes and create objects that inherit attributes from several base classes.
Derived classes can be treated as if they are either of their base classes when inheritance is public. So casts in C++ can have run-time overhead associated with them!
Problems arise when classes being combined share a common base class.
Example:
ClerkTypist class in employ2.cpp.
Now there are two instances of generic Employee data. We can avoid this by making Employee a virtual base class of Clerk and Typist. Thus when we combine, only one instance of the Employee data gets created, and it would only get constructed once. This one instance gets constructed per our instructions in ClerkTypist, and the Employee constructor calls in Clerk and Typist are ignored. The compiler expects these instructions.
We still have to pass on all the args to the Clerk and Typist constructors though even though they may use some of them only to construct Employee base instances. virtual base classes are usually implemented by including a pointer to an instance of the base class in the derived class's memory image rather than an instance as is the usual case. As with virtual functions, a performance hit is taken here since base class members must be accessed indirectly, so the virtual facility should not be used unless it is needed.



0 comments: