virtual base classes

advertisement
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 -
Download