106742360 -1- Chapter 13 Inheritance and Abstract Classes Inheritance in C++ Polymorphism and Virtual Functions Abstract Base classes 106742360 -2- Inheritance Inheritance describes the common attributes and special characteristics among classes. It is a fundamental concept of object-oriented programming. Example - common attributes and special characteristics of species of animals: - Class animal: represents monkeys, cats, birds ..... etc. - They all share the attribute animal. - Each animal is further subdivided into species. A tiger is a cat and a lion is a cat. Animal Tiger Cat Lion 106742360 -3- Class Inheritance Terminology In C++ the concept of inheritance implies that a derived class inherits data and operations from a base class. The derived class may itself be another layer of inheritance. Class hierarchy - a system of classes that uses inheritance Derived Class - subclass - child class Base class - superclass - primary class Base Class // declaration of ordinary C++ class class BaseCL { < data and methods > } Derived class class DerivedCL : public BaseCL { < data and methods > } C++ allows a derived class to be defined with public, private, or protected inheritance. 106742360 -4- Inheritance - Constructors and destructors are never inherited. - Private data members of base class cannot be inherited. - Need to be made protected. - Protected members are available to their own class and to derived classes, but not to the client. Base Class Access determines how the derived Class receives inherited members. Base Class Member Derivation Type Access in Derived Class Client private public protected private inaccessible (base class data) private private inaccessable inaccessable inaccessable private public protected public inaccessible public protected inaccessable accessable inaccessable private public protected protected inaccessible protected protected inaccessable inaccessable inaccessable - When a derived object is created, it constructor is called to initialize its data. - The object inherits data that is initialized by the base class constructor - interaction must occur among the constructors for the base class and the derived class -the beginning of the inheritance chain must be built first since the derived class often uses base class data - When the base class constructor needs parameters the derived class constructor must explicitly call the base class constructor and pass the parameters. It is optional for default constructors - but good practice to do so. BaseCL(int n char ch); // constructor has two parameters DerivedCL ( int n, char ch, int sz ); //Parameter list - constructor derived class //Call constructor BaseCL ( n, ch ) in the initializer list DerivedCL::DerivedCL ( int n, char ch, int sz ) : BaseCL ( n , ch ), data (sz ) { } - Destructors are called in the opposite order. Destructors are automatically generated if the derived class does not have a destructor. 106742360 -5- Inheritance: Example: class NameAdr { public: char name[30]; char addr[30]; char city[10]; char st[2]; char zip[5]; NameAdr get_info(); void print(); NameAdr(); ~NameAdr(); }; class Patient : NameAdr // default is private { char diag_code[2]; double balance; char phone[10]; char dr_code[2]; char bdate[6]; public: void send_bill(); }; What to do if some data members are private and cannot be inherited? Answer : Make them protected. Resolving name Conflicts: - Declarations in the derived class hide member with identical names in the inheritance chain rather than overloading it. 106742360 Inheritance ( continued) class NameAdr { private: char name[30]; char addr[30]; char city[10]; char st[2]; char zip[5]; protected: NameAdr get_info(); public: void print(); NameAdr( ); ~NameAdr(); }; class Patient : private NameAdr { char diag_code[2]; double balance; char phone[10]; char dr_code[2]; char bdate[6]; public: void send_bill(); }; class Supplier : public NameAdr { char vendorcode; double balance; char busphone[10]; float discount; public: void pay_bill(); }; -6- 106742360 -7- Inheritance: Example ( continued) class Employee : protected NameAdr { char dept_code; int withholding; char ins_plan; public: send_paycheck(); }; class Hourly : public Employee { float rate; public: double comp_pay(); }; class Salaried : public Employee { float annual; int yrs_vested; public: double comp_pay(); }; Salaried Jones; Jones.comp_pay(); Jones.print( ); //INVALID 106742360 -8- USING INHERITANCE If a base class has the same function as a derived class, the derived class has priority. class Base { int i; public: void print ( ) { cout << i; } }; class Derived1 : public Base { int j; public: void print( ) { cout << i << endl << j << endl;} }; class Derived2: public Derived1 { int k; public: void print ( ) { cout << i << endl << j << endl << k; }; Base A; Derived1 B; Derived2 C; C.print( ); B.print( ); A.print( ); 106742360 -9- Inheritance with Constructors A derived class must construct its base class!! If a base class requires an argument, the derived class must supply the argument by calling the base class constructor. A derived class only has to worry about its parent. If there is a chain of derived classes, each one takes care of its immediate parent so the constructors "trickle up" properly. Use base member initialization lists!! If a base class constructor requires arguments, the derived class must call the base class constructor, specifying those arguments. If the derived class does not take care of its parent, C++ issues and error because it does not have enough information to construct the base class. If a base class does not need a constructor, or requires only a default constructor, C++ calls the constructor when you define the derived class object. Therefore, you do not need to do anything special with the base class constructor. Derived classes need to worry about specifying a base class constructor only when the default base constructor will not suffice. If the derived class does not have a constructor, an error will result if the base class requires a constructor. If the base class needs only a default constructor, C++ calls it when it defines the derived class object. There might be times when your derived class does not need a constructor for itself, but you must supply one simply to call a base class constructor with arguments. You do not need to worry about the base class destructor. 106742360 Inheritance with Constructors - Example #include <iostream.h> #include <iomanip.h> #include <string.h> class Inventory { int quant, reorder; double price; char* descrip; public: Inventory(int q, int r, double p, char* d); ~Inventory(); void print(); int get_quant(); int get_reorder(); double get_price(); }; Inventory::Inventory(int 1, int r, double p, char* d): quant(q), reorder(r), price(p) { descrip = new char[strlen(d)+1]; strcpy(descrip,d); } Inventory::~Inventory() { delete descrip; } inline void Inventory::print() { cout << "Description: " << descrip << endl; } - 10 - 106742360 Inheritance with Constructors ( continued) class Auto : public Inventory { char* dealer; public: Auto(int,int,double,char*,char*); ~Auto(); void print(); char* get_dealer ( ) {return dealer;} }; Auto::Auto(int q, int r, double p, char* d, char* dea): Inventory(q,r,p,d) { dealer = new char [strlen(dea)+1]; strcpy(dealer,dea); } Auto::~Auto() { delete dealer; } void Auto::print() { cout << setiosflags ( ios::fixed ); cout << "Dealer: " << dealer << endl; } class Machine: public Inventory { char* vendor; public: Machine(int, int, double, char*, char*); ~Machine(); void print(); char* get_vendor() { return vendor;} }; - 11 - 106742360 Inheritance with Constructors ( continued ) Machine::Machine(int q, int r, double p, char* d, char* ven): Inventory(q,r,p,d) { vendor = new char[strlen(ven)+1]; strcpy(vendor,ven); } Machine::~Machine() { delete vendor; } void Machine::print() { cout << setiosflags(ios::fixed); cout << "Vendor: " << vendor << endl; } int main() { Auto car ( 3,1,8745.99, "4-door","GM"); Machine rotor ( 11,5,54.67,"High voltage","Aztec"); cout << setprecision ( 2 ) ; cout << "The car's data: " << endl; car.print(); cout << "Quantity: " << car.get_quant ( ) << endl; cout << "Reorder: " << car.get_reorder ( ) << endl; cout << "Price: $" << car.get_price ( ) << endl; car.Inventory::print ( ); cout << endl << endl; cout << "The machine's data: " << endl; rotor.print(); cout << "Quantity: " << rotor.get_quant ( ) << endl; cout << "Reorder: " << rotor.get_reorder ( ) << endl; cout << "Price: $" << rotor.get_price ( ) << endl; rotor.Inventory::print(); cout << endl << endl; } - 12 - 106742360 OVERRIDING INDIVIDUAL MEMBERS' INHERITED ACCESS class Abc { int i; protected: char* name; public: float stuff; void print ( ) ; Abc get_vals ( ); Abc ( ); ~Abc ( ); }; class Xyz : protected Abc { float additional; public: Xyz(); ~Xyz(); }; class Xyz : protected Abc { float additional; public: Abc::print ( ); Xyz ( ); ~Xyz ( ); }; class Xyz : protected Abc { float additional; Abc::stuff; public: Abc::print(); Xyz ( ) ; ~Xyz ( ); }; - 13 - 106742360 INHERITANCE LIMITATIONS None of the following C++ operations is inheritable: Constructor functions, including copy constructors Destructor functions friend functions overloaded new operators overloaded assignment (=) operators static data members and member functions - 14 - 106742360 MULTIPLE INHERITANCE #include <iostream.h> class Date { int day; int month; int year; public: Date(); ~Date(); void display(); Date get(); void set(); }; class Time { int hour; int minute; int second; public: Time(); ~Time(); void display(); Time get(); void set(); }; class DateTime : public Date , public Time { int digital; public: void display(); }; DateTime watch; watch.display(); watch.Date::display(); watch.Time::display(); - 15 - 106742360 - 16 - VIRTUAL BASE CLASSES A base class is shared by two other classes. class Inventory { }; class Auto: public Inventory { }; class Machine: public Inventory { }; class CustOrder : public Auto, public Machine { }; If you want to share a base class, insert the keyword, virtual, in the base class access list of the derived classes. virtual ensures that a copy of the base class will not be used every time the base class is inherited. class Auto : virtual public Inventory { }; class Machine : virtual public Inventory { }; class Inventory { int quant; public: Inventory(int Q) : {quant = Q;} }; class Auto : virtual public Inventory { char type; public: Auto(char T, int Q) : Inventory(Q) {type = T;} }; 106742360 - 17 - class Machine : virtual public Inventory { float weight; public: Machine(float W, int Q) : Inventory(Q) {weight = W;} }; class CustOrder : public Auto, public Machine { int ordnum; public: CustOrder(int O, int Q, char C, float W) : ordnum(O), Inventory(Q), Auto(C,Q), Machine(W,Q) { }; }; The CustOrder constructor constructs itself and also constructs the base class and then its derived classes. Auto car ( ' X ', 7 ); Machine widget ( 232.34 , 6 ) ; each of these calls the constructor. virtual keyword is meaningless to them because they only inherit one copy of Inventory. 106742360 - 18 - THE NEED FOR MULTIPLE INHERITANCE An OOP language doesn't require multiple inheritance. If you find yourself wanting to inherit from two base classes, reconsider the inheritance. Single inheritance is safer and easier to maintain. RESTRICTING INHERITING FUNCTIONS You want the inherited class to contain fewer member than its parent class. You cannot actually decrease the number of inherited members in C++, but you can change the way they work. If the base class contains a data member that you don't want in the derived class, you can change the data to private to limit its access from anywhere else in the program. Some C++ programmers redefine inherited member functions they don't want so that the functions contain a null body. 106742360 - 19 - Polymorphism Greek for many formsThe ability to have different objects derived from the same class respond to the same command differently. EARLY AND LATE BINDING Early binding (sometimes called static binding) - Refers to the requirement that all direct function calls be known at compile time ( C does this ). Compiler searches through the program looking for function calls so that it can resolve those function calls during compilation. C includes the called function address in the object code. The compiler binds the function calls directly into its object code. Late binding (sometimes called dynamic binding)- You can achieve both (late and early in C++ and actually can achieve some late binding in C also.) void add_fun(void); void change_fun(void); void print_fun(void); void quit_pgm(void); void (*menu[]) (void) = {add_fun, change_fun, print_fun, quit_pgm}; void main() { int ans; clrscr(); do { printf("\n\nDo you want to:\n\n"); printf("1. Add records\n"); printf("2. Change records\n"); printf("3. Print records\n"); printf("4. Quit program\n"); scanf("%d", &ans); menu[ans-1]( ); }while(1); 106742360 - 20 - Polymorphism ( continued) void add_fun() { } void change_fun() { } void print_fun() { } void quit_pgm() { exit(0); } Due to the array of function pointers, the compiler can put off the binding of the function call into the object code. menu[ans - 1] ( ) ; The compiler will insert the menu table in the object code. At runtime, the order of the functions is determined dynamically. When the compiled program gets to the function call statement, it uses ans to search the table of pointers, and finds the address of the function that it should actually call. 106742360 INHERITANCE WITHOUT VIRTUAL FUNCTIONS #include <iostream.h> #include <string.h> class Person { char* name; int age; public: Person(char* , int); ~Person() { delete name; } void display(); }; Person::Person(char* n, int a) { name = new char [strlen(n) + 1]; strcpy(name, n); age = a; } void Person::display() { cout << "\nName:\t" << name << endl; cout << "Age:\t" << age << endl; } class Student : public Person { char* id; public: Student(char*, char*, int); ~Student() {delete id;} void display(); }; Student::Student(char* i, char* n, int a) : Person(n,a) { id = new char[strlen(i) + 1]; strcpy(id i); } - 21 - 106742360 Inheritance without Virtual Functions ( continued) void Student::display() { Person::display(); cout << "ID:\t << id << endl; } class Teacher : public Person { double salary; public: Teacher(double s, char* n, int a) : salary(s), Person(n,a) {} }; void main() { Person human("Ray Smith", 35); Student kid("8NB5" , "Tim Moriarty" , 18 ); Teacher instructor(150000.00, "Jo Ann Smith", 29); human.display(); kid.display(); instructor.display(); } - 22 - 106742360 - 23 - Late Binding C++ uses a table similar to the function pointer table to perform late binding. It is with late binding that C++ achieves true polymorphism. Late binding is inherent with virtual functions. They offer a way for C++ to resolve function calls at runtime instead of at compile time. Note: Do not confuse virtual functions with virtual base classes. You use virtual functions with inheritance. To declare a virtual function, put the keyword virtual before the function name in the class header. class Children { int age; char* name; public: int get_age(); char* get_name(); virtual void show(); virtual int add(int, char*); }; It is with late binding that C++ achieves true polymorphism. Late binding is inherent with virtual functions. They offer a way for C++ to resolve function calls at runtime instead of at compile time. Note: Do not confuse virtual functions with virtual base classes. You use virtual functions with inheritance. To declare a virtual function, put the keyword virtual before the function name in the class header. Virtual tells C++ to bind function later, at runtime, not at compile time. C++ builds a table in memory of future function addresses vtable Compiler directs calls to the virtual function to the vtable and not to a specific address. At runtime, the actual address of the function being called is resolved through the table entry. 106742360 - 24 - Why Do we require VIRTUAL FUNCTIONS ? C++ allows you to create an array of pointers to different types of objects as long as those objects all belong to the same class inheritance hierarchy. You must use the base class when defining the array. Any derived class instance can be pointed to by a base class pointer. (but not the reverse) Example without virtual functions: class Person { char* name; int age; public: Person(char* , int); ~Person() { delete name; } void display(); }; Person::Person(char* n, int a) { name = new char [strlen(n) + 1]; strcpy(name, n); age = a; } void Person::display() { cout << "\nName:\t" << name << endl; cout << "Age:\t" << age << endl; } 106742360 Example without Virtual functions ( continued ) class Student : public Person { char* id; public: Student(char*, char*, int); ~Student() {delete id;} void display(); }; Student::Student(char* i, char* n, int a) : Person(n,a) { id = new char[strlen(i) + 1]; strcpy(id i); } void Student::display() { Person::display(); cout << "ID:\t << id << endl; } class Teacher : public Person { double salary; public: Teacher(double s, char* n, int a) : salary(s), Person(n,a) {} }; - 25 - 106742360 - 26 - Virtual Functions ( continued ) void main() { Person human("Ray Smith", 35); Student kid("8NB5" , "Tim Moriarty" , 18 ); Teacher instructor(150000.00, "Jo Ann Smith", 29); Person* ptr[3]; ptr[0] = &human; ptr[1] = &kid; ptr[2] = &instructor; for ( int ctr = 0; ctr < 3; ctr++ ) { ptr[ctr]->display(); } human.display(); kid.display(); instructor.display(); } The Problem: At compile time, the compiler has no idea exactly what to do here. The compiler doesn't know which object will be pointed to by ptr[ctr] and will also not know which display() function to call!! At compile time, C++ decides that it will use the base class display for the statement, ptr[ctr]->display(), no matter what the ptr[] element contains at runtime. This is early binding. But what you really want to do is use the student display() is the pointer contains a student object, and use the base class display() if the pointer contains a person or a teacher object and make up your mind which one to use at runtime not compile time. If you virtualize a base class function, C++ knows to perform late binding. Virtual tells the C++ compiler you want to send the same message to different objects and to wait by not assuming that the base class will contain the function to call. This describes polymorphism: you want to pass the same message to different objects. C++ will set up a table of pointers at compile time, using the table at runtime to resolve function call addresses. kfv-COD 106742360 Example with Virtual Functions (continued ) class Person { char* name; int age; public: Person(char* , int); ~Person() { delete name; } virtual void display(); }; Person::Person(char* n, int a) { name = new char [strlen(n) + 1]; strcpy(name, n); age = a; } void Person::display() { cout << "\nName:\t" << name << endl; cout << "Age:\t" << age << endl; } class Student : public Person { char* id; public: Student(char*, char*, int); ~Student() {delete id;} void display(); }; Student::Student(char* i, char* n, int a) : Person(n,a) { id = new char[strlen(i) + 1]; strcpy(id i); } - 27 - 106742360 Example with Virtual Functions ( continued ) void Student::display() { Person::display(); cout << "ID:\t << id << endl; } class Teacher : public Person { double salary; public: Teacher(double s, char* n, int a) : salary(s), Person(n,a) {} }; void main() { Person human("Ray Smith", 35); Student kid("8NB5" , "Tim Moriarty" , 18 ); Teacher instructor(150000.00, "Jo Ann Smith", 29); Person* ptr[3]; ptr[0] = &human; ptr[1] = &kid; ptr[2] = &instructor; for ( int ctr = 0; ctr < 3; ctr++ ) { ptr[ctr]->display(); } human.display(); kid.display(); instructor.display(); } - 28 - 106742360 - 29 - THE VIRTUAL TABLE Also referred to as the vtable. The vtable provides the means for polymorphism. Each class that contains virtual functions, as well as each derived class, has its own vtable. The example program has a virtual function in the base class, with three classes and three classes (person,student,teacher). The compiler will set up three vtables, one for each class. The vtable contains addresses of each virtual function in the base class. This example has only one virtual function, so there is only one address. When C++ first compiles this program. Every object of a class contains a pointer, called the vptr, to the vtable. (e.g. human, kid, and instructor) The vptr contains the address of that variable's class vtable and the vtable points to the virtual functions in the class. 106742360 - 30 - OVERRIDING THE VIRTUALIZATION Just because a function is virtual, and just because the same function exists in a derived class, doesn't mean you must use the derived function. You can override the defaults. If you want to call the base class function instead of a derived class function, even when use are using a derived class object, preface the object with the base class scope resolution operator. ptr[1]->Person::display(); The base class virtual functions explain what each derived object is capable of doing. Some derived functions might have additional features, and some derived objects might redefine the base class functions, but each derived class will have functions named the same as the base class. 106742360 - 31 - PURE VIRTUAL FUNCTIONS AND ABSTRACT BASE CLASSES Pure virtual functions contain no code and are used as a pattern for all derived classes. virtual void display() = 0; You cannot define instances for a class that contains a pure virtual function. The class becomes an abstract base class and provides the framework for inherited classes and defines the makeup of the family of classes. Any class that contains one or more pure virtual functions is an abstract base class and you cannot define any instances of the class. Destructors: For every base class from which you derive other classes, make the base class destructor virtual. Now, if you delete an instance through its pointer or a reference, C++ will call the object's own destructor. Ex: class BaseCL { ... public: BaseCL ( ... ); // allocate 7 element array ~BaseCL ( void); // deallocate ( not virtual ) } class DerivedCL : public BaseCL { .... public: DerivedCL ( .... ); // allocate 7 element array ~DerivedCL (void); // deallocate ( not virtual ) } Base *p = new DerivedCL( ); // construct a derived class object delete p; The dynamic data generated by the derived class are not destroyed. If the Base destructor is declared virtual, the derived destructor is called. 106742360 - 32 - Abstract Base Class An abstract Base class acts like a template for its derived classes. It may contain data and methods that are shared by all derived classes. By using pure virtual functions it provides a declaration of the public methods that must be implemented by the derived class. Example: Abstract List class template <class T> class List { protected: int size; // number of elements in list - updated by derived class public: // constructor List(void); // List access methods virtual int ListSize(void) const; virtual int ListEmpty(void) const; virtual int find ( T& item) = 0; // list modification methods virtual void Insert ( const T& item ) = 0; virtual void Delete ( const T& item ) = 0; virtual void ClearList(void) = 0; }; 106742360 List Method Implementation // constructor sets size to 0 template <class T> int List<T>::List(void) : size (0) {} // return the list size template <class T> int List<T>::Listsize(void) const { return size; } // test for empty list template <class T> int List<T>::Listempty(void) const { return size == 0 ; } - 33 - 106742360 - 34 -