15.3 Pointer to Base and Derived Class Objects
15.5 Rules for Virtual Functions
15.9 Working of Virtual Functions
15.10 Virtual Functions in Derived Classes
One of the key features offered by C++ is polymorphisms. It is essential to know the concepts of polymorphisms and associated topics. In this chapter, we are going to learn and implement static (early) binding, dynamic (late) binding, polymorphisms, and virtual functions. An attempt is made to illustrate every point of this new topic in an easy way, and complicated subtopics are explained in a simple way.
The word poly means many, and morphism means several forms. Both the words are derived from Greek language. Thus, by combining these two words, a new whole word called polymorphism is created, which means various forms. We have learnt about overloading of functions and operators. It is also one type of polymorphism. The information pertaining to various overloaded member functions and arguments is known to the compiler while compiling. Thus, the compiler selects an appropriate function and collects the arguments at compile time. This is called early binding or static binding. This is also known as compile time polymorphism.
In C++, the function can be bound at either compile time or run time. Deciding a function call at compile time is called compile time or early or static binding. Deciding a function call at run time is called run time or late or dynamic binding. Dynamic binding permits to suspend the decision of choosing a suitable member function until run time. Two types of polymorphism are shown in Figure 15.1.
POLYMORPHISMS
A polymorphism is a technique in which various forms of a single function can be defined and shared by various objects to perform an operation.
Fig. 15.1 Polymorphisms in C++
Though C++ is an object-oriented programming language, it is very much inspired by procedural language. A program in C++ is executed sequentially line by line. Each line of the source program after translation is in the form of machine language. Each line in the machine language is assigned a unique address. Similarly, when a function call is encountered by the complier during execution, the function call is translated into machine language, and a sequential address is provided. Thus, binding refers to the process that is to be used for converting functions and variables into machine language addresses. The C++ supports two types of binding: static or early binding and dynamic or late binding.
In this section, we are going to focus on static or early binding. Even though similar function names are used at many places, their references and their positions are indicated explicitly by the compiler. Their ambiguities are fixed at compile time. Consider the following example.
{
int d;
public:
void display() {------} // base class member function
};
class second : public first // derived class
{
int k:
public:
void display() {-------} // member function of derived class
}
In the above program, base and derived classes have a similar member function name display() but after conversion into machine language, addresses for two display() functions are different and unique. Thus, the compiler instructs the CPU to jump to the different addresses when similar display() function calls are executed at various places in the program. This is not an overloaded function, because its prototype is the same and it is defined in different classes.
The following program is based on early binding:
15.1 Write a program to invoke function using scope access operator.
#include<iostream.h>
#include<conio.h>
class first
{
int b;
public:
first() {b=10;}
void display() {cout<<“ b=”<<b;}
};
class second: public first
{
int d;
public:
second() {d=20;}
void display() {cout<<“ d=”<<d;}
};
int main()
{
clrscr();
s.first::display(); // Invokes base class function
s.display(); // Invokes derived class function
return 0;
}
OUTPUT
b = 10
d = 20
Explanation: In the above program, the class first is a base class and class second is a derived class. Both the classes contain one integer data member and member function. The display() function is used to display the content of the data member. Both the classes contain a similar function name. In function main(), s is an object of the derived class second. Hence, in order to invoke the display() function of the base class, the scope access operator is used. When base and derived classes have similar function names, in such a situation, it is very essential to provide information to the compiler at run time about the member functions. The mechanism that provides run-time selection of a function is called a polymorphism.
A virtual keyword plays an important role in late binding. Before introducing virtual functions, let us have a look at the given below program. The following program shows what happens when functions are not declared virtual. This is an example of early binding.
15.2 Write a program to invoke member function of base and derived class using pointer of base class.
#include<iostream.h>
#include<conio.h>
class first
{ int b;
public:
first() {b=10;}
void display() {cout<<“ b=” <<b;}
};
class second: public first
{
int d;
public:
second() {d=20;}
void display() {cout<<“ d=”<<d;}
};
int main()
{
clrscr();
first f,*p;
p=&f;
p->display();
p=&s;
p->display();
return 0;
}
OUTPUT
b = 10
b = 10
Explanation: In the above program, the class first is a base class and class second is a derived class. The variable f is an object of the base class, and s is an object of the derived class. The pointer *p is an object pointer of the base class. The address of the base class object is assigned to pointer p, and display() function is called. Again, the address of the derived class is assigned to pointer p, and display() function is called. Both the times, the display() function of base class is executed. This is so, because though in the second call p contains the address of the object of the derived class, yet the display() function of the base class replaces the existence of the display() function of the derived class. In order to execute various forms of the same function defined in the base, derived class, run-time binding is necessary, and it can be achieved using the virtual keyword.
In the case of a few programs, it is impossible to know which function is to be called until run time. This is called dynamic binding. Dynamic binding of member functions in C++ can be done using the virtual keyword. The member function followed by the virtual keyword is called a virtual function. Consider the following example:
class first // base class
{
int d;
public:
virtual void display() {------} // base class member function
};
class second: public first // derived class
{
int k:
public:
virtual void display() {-------} // member function of derived class
}
In the example of the above program, base and derived classes have a similar member function display() preceded by the keyword virtual. The various forms of virtual functions in base and derived classes are dynamically bound. The references are detected from the base class. All virtual functions in derived classes are considered virtual, supposing they match the base class function exactly in the number and types of parameters sent. If there is no match between these functions, they will be assumed as overloaded functions. The virtual function should be defined in the public section. If the function is declared virtual, the system will use dynamic (late) binding, which is carried out at run time. Otherwise, early or compile-time binding is used.
Dynamic binding can be implemented with function pointers. In this method, the pointer points to a function instead of a variable. A simple programming example is given below based on late binding.
15.3 Write a program to perform few arithmetic operations on floating numbers using functions. Use function pointer (Late binding).
#include<iostream.h>
#include<conio.h>
float add(float m, float n)
{
return m+n;
}
float sub(float m, float n)
{
return m-n;
}
float mul(float m, float n)
{
return m*n;
}
int main()
{
clrscr();
float x,y;
cout<<“Enter two numbers:”;
cin>>x>>y;
int task;
do
{
cout<<“Enter task (1=add, 2=sub, 3=mul):”;
cin>>task;
}
while (task< 1 || task > 3);
// Function pointer named pt is created
switch (task)
{
case 1:
pt=add;
break;
case 2:
pt=sub;
break;
case 3:
pt=mul;
break;
}
cout<<“Result of operation is:” << pt(x,y) << endl;
return 0;
}
OUTPUT
Enter two numbers: 2.5 2.5
Enter task (1=add, 2=sub, 3=mul): 3
Result of operation is : 6.25
Explanation: In the above program, instead of calling functions directly, we have called them through function pointer pt. The complier is unable to use static or early binding in this case. In this program, the late binding concept is implemented. The compiler has to read the addresses held in the pointers toward different functions. Until run time, decisions are not taken as to which function needs to be executed; hence, it is late binding.
In inheritance, the properties of existing classes are extended to the new classes. The new classes that can be created from the existing base class are called as derived classes. The inheritance provides the hierarchical organization of classes. It also provides the hierarchical relationship between two objects and indicates the shared properties between them. All derived classes inherit properties from the common base class. Pointers can be declared to the point base or derived class. Pointers to objects of the base class are type compatible with pointers to objects of the derived class. A base class pointer can point to objects of both the base and derived class. In other words, a pointer to the object of the base class can point to the object of the derived class; whereas a pointer to the object of the derived class cannot point to the object of the base class, as shown in Figure 15.2.
Fig. 15.2 Type compatibility of base and derived class pointers
15.4 Write a program to access members of base and derived class using pointer objects of both classes.
#include<iostream.h>
#include<constream.h>
class W
{
protected:
int w;
public:
W (int k) { w=k; }
void show()
{
cout<<“ In base class W”;
cout<<“ W=”<<w; };
};
class X: public W
{
protected:
int x;
public:
X (int j, int k): W( j)
{
x=k;
}
void show()
{
cout<<“ In class X”;
cout<<“ w=”<<w;
cout<<“ x=”<<x;
}
class Y: public X
{
public:
int y;
};
void main()
{
clrscr();
W *b;
b = new W(20); // pointer to class W
b->show();
delete b;
b = new X(5,2); // pointer to class X
b->show();
((X*)b)->show();
delete b;
X x(3,4);
X *d=&x;
d->show();
}
OUTPUT
In base class W
W=20
In base class W
W=5
In class X
w=5
x=2
In class X
w=3
x=4
Explanation: In the above program, the class W is a base class. The X is derived from W, and class Y is derived from class X. Here, the type of inheritance is multilevel inheritance. The variable *b is a pointer object of the class W. The statement b = new W (20); creates a nameless object and returns its address to the pointer b. The show() function is invoked by the pointer object b. Here, the show() function of base class W is invoked. Using delete operator, the pointer b is deleted.
The statement b = new X (5,2); creates a nameless object of class X and assigns its address to the base class pointer b. Here, it should be noted that the object b is a pointer object of the base class, and it is initialized with the address of the derived class object. Again, the pointer b invokes the function show() of the base class and not the function of class X (derived class.). To invoke the function of the derived class X, the following statement is used:
((X*)b)->show();
In the above statement, typecasting (upcasting) is used. The upcasting forces the object of class W to behave as if it were the object of class X. This time, the function show() of class X (derived class) is invoked. The process of obtaining the address of a derived class object, and treating it as the address of a base class object is known as upcasting.
The statement X x(3,4); creates object x of class X. The statement X *d = &x;
declares the pointer object d of the derived class X and assigns the address of x to it. The pointer object d invokes the derived class function show(). Figure 15.3 shows a pictorial representation of the above explanation.
Fig. 15.3 Type compatibility of base and derived class pointers
Here, the pointer of class W points to its own class as well as its derived class. The pointer of derived class X can point to its own class but cannot point to its base class.
Virtual functions of the base class should be redefined in the derived classes. The programmer can define a virtual function in a base class, and can then use the same function name in any derived class, even if the number and type of arguments are matching. The matching function overrides the base class function of a similar name. Virtual functions can only be member functions. We can also declare the functions as given below:
int Base::get (int) and int Derived::get(int) even when they are not virtual.
The base class version is available to derived class objects via scope override. If they are virtual, only the function associated with the actual type of the object is available. With virtual functions, we cannot alter just the function type. It is illegal, therefore, to redefine a virtual function so that it varies only in the return type. If two functions with a similar name have different arguments, C++ compiler considers them different, and the virtual function mechanism is dropped.
15.5 Write a program to declare virtual function and execute the same function defined in the base and derived class.
#include<iostream.h>
#include<conio.h>
class first
{
int b;
public:
first() {b=10;}
virtual void display() {cout<<“ b=” <<b;}
};
class second : public first
{
int d;
public:
second() {d=20;}
void display() {cout<<“ d=”<<d;}
};
int main()
{
clrscr();
second s;
p=&f;
p->display();
p=&s;
p->display();
return 0;
}
OUTPUT
b = 10
d = 20
Explanation: The above program is similar to the previous one. The only difference is that a virtual keyword precedes the display() function of the base class as per the statement virtual void display() {cout<<“ b = ” <<b;}. The virtual keyword does the run-time binding. In the first call, the display() function of the base class is executed and in the second call, that is, after assigning the address of the derived class to pointer p, displays() function of the derived class is executed.
15.6 Write a program to use pointer for both base and derived class and call the member function. Use virtual keyword.
VIRTUAL FUNCTIONS
#include<iostream.h>
#include<conio.h>
class super
{
public:
virtual void display() {cout<<“ In function display() class super”;}
virtual void show() {cout<<“ In function show() class super”;}
}
;
class sub : public super
{
public:
void display() {cout<<“ In function display() class sub”;}
void show() {cout<<“ In function show() class sub”;}
};
int main()
{
super S;
sub A;
super *point;
cout<<“ Pointer point points to class super ”;
point=&S;
point->display();
point->show();
cout<<“ Now Pointer point points to derived class sub ”;
point=&A;
point->display();
point->show();
return 0;
}
OUTPUT
Pointer point points to class super
In function display() class super
In function show() class super
Now Pointer point points to derived class sub
In function display() class sub
In function show() class sub
Explanation: In the above program, the base class super and the derived class sub have member functions a similar name. They are display() and show(). In function main(), the variable S is an object of class super, and the variable A is an object of derived class sub. The pointer variable point is a pointer to the base class. The address of object S is assigned to the pointer S. The pointer calls both the member functions. Similarly, the variable A is an object of the derived class sub. The address of A is assigned to the pointer point and again, the pointer calls the member functions.
The member functions of the base class are preceded by the keyword virtual. If the virtual keyword is removed, in both the function calls, the member functions of the base class are executed. The member functions for the derived class are not executed, though the pointer has the address of the derived class. If the virtual keyword is not removed, firstly, the member function of the base class is executed, and then member function of derived class.
Polymorphism refers to late or dynamic binding; that is, the selection of an entity is decided at run time. In class hierarchy, methods with similar names can be defined, which perform different tasks, and then, the selection of the appropriate method is done using dynamic binding. Dynamic binding is associated with object pointers. Thus, addresses of different objects can be stored in an array to invoke functions dynamically. The following program explains this concept:
15.7 Write a program to create array of pointers. Invoke functions using array objects.
#include<iostream.h>
#include<constream.h>
class A
{
public:
virtual void show()
{
cout<<“A ”;
}
};
class B : public A
{
public:
void show() {cout<<“B ”;}
};
class C : public A
{
public:
void show()
{cout<<“C ”;
}
};
class D : public A
{
public:
void show()
{
cout<<“D ”;
}
};
class E : public A
{
public:
void show()
{
cout<<“E”;
}
};
void main()
{
clrscr();
A a;
C c;
D d;
E e;
A *pa[]={&a,&b,&c,&d,&e};
for ( int j=0;j<5;j++)
pa[j]->show();
}
OUTPUT
A
B
C
D
E
Explanation: In the above program, class A is a base class. The classes B, C, D, and E are classes derived from class A. All these classes have a similar function show(). In function main(), a, b, c, d, and e are objects of classes A, B, C, D, and E, respectively. The function show() of the base class is declared virtual. An array of pointer *pa is declared, and it is initialized with addresses of the base and derived class objects; that is, a, b, c, d, and e. Using for loop and array, each object invokes function show(). The output is as shown above. If the base class function show() is non-virtual, then the very time function show() of base class is executed. Figure 15.4 illustrates this concept more clearly.
Fig. 15.4 Early and late binding of functions
In practical applications, the member function of the base class is rarely used for doing any operation; such functions are called do-nothing functions, dummy functions, or pure virtual functions. The do-nothing functions or pure functions are always virtual functions. Usually, pure virtual functions are defined with a null body. This is so, because derived classes should be able to override them. Any normal function cannot be declared as a pure function. After the declaration of a pure function in a class, the class becomes an abstract class. It cannot be used to declare any object. Any attempt to declare an object will result in the error “cannot create instance of abstract class.” The pure function can be declared as follows:
Declaration of pure virtual function
virtual void display() =0; // pure function
In the above declaration of the function, the display() is a pure virtual function. The assignment operator is not used to assign zero to this function. It is used just to instruct the compiler that the function is a pure virtual function and that it will not have a definition.
A pure virtual function declared in the base class cannot be used for any operation. The class containing the pure virtual function cannot be used to declare objects. Such classes are known as abstract classes or pure abstract classes. Anyone who attempts to declare an object from the abstract class would be reported an error message by the compiler. In addition, the compiler will display the name of the virtual function present in the base class. The classes derived from the pure abstract classes are required to re-declare the pure virtual function. All other derived classes without pure virtual functions are called concrete classes. The concrete classes can be used to create objects. A pure virtual function is similar to an unfilled container that the derived class is made to fill.
15.8 Write a program to declare pure virtual functions.
#include<iostream.h>
#include<conio.h>
class first
{
protected:
int b;
public:
first() {b=10;}
virtual void display() =0; // pure function
};
class second: public first
{
int d;
public:
second() {d=20;}
void display() {cout<<“ b=”<<b <<“ d=”<<d;}
};
int main()
{
clrscr();
first *p;
// p->display // abnormal program termination
second s;
p=&s;
p->display();
return 0;
}
OUTPUT
b= 10 d = 20
Explanation: In the above program, the display() function of the base class is declared a pure function. The pointer object *p holds the address of the object of the derived class and invokes the function display() of the derived class. Here, the function display() of the base class does nothing. If we try to invoke the pure function using the statement p->display() as per the given remarks in the above program (//p->display//abnormalprogram termination), the program is terminated with the error “abnormal program termination.”
Abstract classes are similar to a skeleton on which new classes are designed to assemble a well-designed class hierarchy. The set of well-tested abstract classes can be used, and the programmer only extends them. Abstract classes containing virtual functions can be used to help in program debugging. When various programmers work on the same project, it is necessary to create a common abstract base class for them. The programmers are restricted to create a new base class.
The development of such software can be demonstrated by creating a header file. The file abstract.h is an example of a file containing an abstract base class that is used for debugging purpose. Contents of the file abstract.h are as follows:
Contents of abstract.h header file
#include<iostream.h>
struct debug
{
virtual void show()
{
cout<<“ No function show() defined for this class”;
}
};
15.9 Write program to use abstract class for program debugging.
#include“abstract.h”
#include<constream.h>
class A : public debug
{
int a;
public:
A(int j=0) {a=j;
}
void show() {cout<<“ In class A a=”<<a;}
};
class B : public debug
{
int b;
public:
B (int k ) {b=k;}
};
void main()
{
clrscr();
A a(1);
B b(5);
a.show();
b.show();
}
OUTPUT
In class A a=1
No function show() defined for this class
Explanation: Observe the contents of file abstract.h. The struct debug contains the virtual function show(). This function is declared in the file abstract.h and the same is inserted using #include directive in the above program. The classes A and B are derived from class debug, which is defined in the header file abstract.h. In function, a and b are the objects of classes A and B, respectively. The statement a.show(); invokes the function show(), and the value of a is displayed. The object b also invokes the function show(). However, the class B does not have the function show(). Hence, the function show() of an abstract base class is executed, which displays a warning message.
Traditional languages do not provide such facilities. An abstract class develops into a dominant and powerful interface when the software system undergoes various changes. It is essential to confirm that the debugging interface is accurately constructed. If changes are made in the actual project, it is compulsory to add appropriate methods to the abstract base class. In case the programmer needs to define a function warn() to the class debug, then the header file can be updated. The contents of the file would be as follows:
Contents of abstract.h header file
#include<iostream.h>
struct debug
{
virtual void show()
{
cout<<“ No function show() defined for this class”;
}
};
virtual void warn()
{
cout<<“ No function warn() defined for this class”;
}
};
While defining such an abstract class, the following points should be kept in mind:
Before learning about the mechanism of virtual functions, let us revise a few points related to virtual functions:
The following programs illustrate the step-by-step working of virtual functions:
15.10 Write a program to define virtual and non-virtual functions and determine the size of the objects.
#include<iostream.h>
#include<conio.h>
class A
{
private:
int j;
public:
virtual void show() {cout<<endl<<“In A class”;}
};
class B
{
private:
int j;
public:
void show() {cout<<endl<<“in B class”;}
};
class C
{
public:
void show() {cout<<endl<<“In C class”;}
};
void main()
{
clrscr();
A x;
B y;
C z;
cout<<endl<<“Size of x=”<<sizeof (x);
cout<<endl<<“Size of y=”<<sizeof (y);
cout<<endl<<“Size of z=”<<sizeof (z);
}
OUTPUT
Size of x = 4
Size of y = 2
Size of z = 1
Explanation: In the above program, the class A has only one data element of integer type, but the size of the object displayed is 4. The size of the object of class B that contains a one-integer data element is two, and finally, the size of the object of class C is displayed as one, even if the class C has no data element.
The function show() of class A is prefixed by the virtual keyword. Without the virtual keyword, the size of objects would be 2,2 and 1. The size of object x with a virtual function in class A is the addition of data member int (2 bytes) and void pointers (2 bytes). When a function is declared as virtual, the compiler inserts a void pointer. In class C, even if it has no object, the size of the object displayed is 1, and this is due to the compiler, who assumes the size of object z not to be zero, as every object should have an individual address. Hence, minimum size one is considered. The minimum nonzero positive integer is one.
To perform late binding, the compiler establishes VTABLE (virtual table) for every class and its derived classes having a virtual function. The VTABLE contains addresses of the virtual functions. The compiler puts the address of the virtual functions in the VTABLE. If no function is redefined in the derived class that is defined as virtual in the base class, the compiler takes the address of the base class function.
When an object of the base or derived class is created, a void pointer is inserted in the VTABLE, called VPTR (vpointer). The VPTR points to the VTABLE. When a virtual function is invoked, using the base class pointer, the compiler speedily puts a code to obtain the VPTR and searches for the address of the function in the VTABLE. In this way, an appropriate function is invoked, and dynamic binding takes place.
The VPTR should be initialized with the beginning address of the apposite VTABLE. When the VPTR is initialized with the apposite VTABLE, the type of the object can be determined by itself. However, it is useless if is applied at the point when a virtual function is invoked.
Assume a base class pointer object points to the object of a derived class. If a function is invoked using the base class pointer, the compiler uses a different code to accomplish the function call. The compiler begins from the base class pointer. The base class pointer holds the address of the derived class object. With the aid of this address, the VPTR of the derived class is obtained. Via VPTR, the VTABLE of the derived class is obtained. In the VTABLE, the address of the function being invoked is acquired and the function is called. All the above processes are handled by the compiler, and the user need not worry about them. The following program makes the concept clearer:
15.11 Write a program to define virtual member functions in derived classes.
#include<iostream.h>
#include<conio.h>
class shop
{
public:
virtual void area() {cout<<endl<<“In area of shop”;}
virtual void rent() {cout<<endl<<“In rent of shop”;}
void period() {cout<<endl<<“In period of shop”;}
};
class shopA : public shop
{
public:
void area() {cout<<endl<<“In area of shopA”;}
void rent() {cout<<endl<<“In rent of shopA”;}
};
class shopB : public shop
{
public:
void area() {cout<<endl<<“In area of shopB”;}
void rent() {cout<<endl<<“In rent of shopB”;}
void period() {cout<<endl<<“In period of shopB”;}
};
class shopC: public shop
{
void area() {cout<<endl<<“In area of shopC”;}
};
void main()
{
clrscr();
shop *p;
shop s;
p=&s;
p->area();
p->rent();
p->period();
shop *sa,*sb,*sc;
shopA a;
shopB b;
shopC c;
sa=&a;
sb=&b;
sc=&c;
sa->area();
sa->rent();
sb->area();
sb->rent();
sc->area();
sc->rent();
sa->period();
sb->period();
shop d;
d.area();
shopA e;
shopC f;
f.rent();
}
OUTPUT
In area of shop
In rent of shop
In period of shop
In area of shopA
In rent of shopA
In area of shopB
In rent of shopB
In area of shopC
In rent of shop
In period of shop
In period of shop
In area of shop
In area of shopA
In rent of shop
Explanation: In the above program, four classes are declared, as shown in Figure 15.5.
Fig. 15.5 Base and derived classes
Fig. 15.6 VPTR AND VTABLES
As discussed earlier, a VTABLE is formed for each class having a virtual function and for the derived class of the same class. The VTABLE is formed for the following classes: shop, shopA, shopB, and shopC. All of the above four VTABLES hold the address of virtual functions. In addition, the compiler would place a VPTR that points to the particular VTABLE, as shown in Figure 15.6.
The class shopC is without functions rent(). Therefore, the VTABLE holds the address of the base class rent() function. Consider the following statements:
shop *p; // Base class object pointer
shop s; // object of base class
p = &s; // Address of s is assigned to p
The pointer p contains the address of the object s. Now, consider the following statements:
p->area(); // Invokes function area() of base class
p->rent(); // Invokes function rent() of base class
p->period(); // Invokes function period() of base class
From the above functions, first two function area() and rent() are virtual, and period() is a non-virtual function. Though the address of the base class object or derived class object is stored in the base class pointer, the function of the base class is invoked, because the period() is a non-virtual function. The member function area() is declared as virtual in the base class. All the three derived classes shopA, shopB, and shopC contain the function area(). The execution of these functions depends on the address stored in the base class pointer. For example, p contains the address of object a; the functions of class shopA are invoked. Similarly, for storing addresses of objects b and c, functions of classB and classC can be invoked.
p = &s; // Address of s is assigned to p
In the above statement, p contains the address of the object s (base class). Therefore, when the function area() is invoked by the pointer p, VPTR is created from the object s. With the help of VPTR, VTABLE of the class shop is obtained, and the address of the function area() for class shop is accessed. With the aid of an address, shop::area() is finally invoked. Consider the following statements:
shop *sa; // object pointer declaration
shopA a; // object of derived class
sa = &a; // assigns address of derived class object to base class pointer
sa->area(); // invokes function area()
sa->rent(); // invokes function rent()
In the statement sa = &a; the address of the derived class object is stored in the base class pointer. When the function area() is invoked, the VPTR of object a is used to obtain the VTABLE of class shopA. From this VTABLE, the address of shopA :: area() and shopA :: rent are obtained and finally invoked. Likewise, for objects b and c, binding is achieved.
Consider the following statements:
sa->period();
sb->period();
Function period() is a non-virtual function. The VTABLE is not used to call the function shop :: period(). In addition, the function period() is not redefined in the derived classes. Now, concentrate on the following statements:
shop d; // Base class object
d.area();
shopA e; // Derived class object
e.area();
shopC f; // Derived class object
f..rent();
In the above statements, dynamic binding is not performed, and, hence, VPTR and VTABLE are not created. The functions are called as usual.
C++ places addresses of the virtual member function in the virtual table. When these member functions are called, the accurate address is obtained from the virtual table. This entire procedure takes time. Therefore, the virtual function makes program execution a bit slow. Conversely, it provides better flexibility.
We know that when functions are declared virtual in the base class, it is mandatory to redefine virtual functions in the derived class. The compiler creates VTABLES for the derived class and stores addresses of functions in it. In case the virtual function is not redefined in the derived class, the VTABLE of the derived class contains the address of the base class virtual function. Thus, the VTABLE contains addresses of all functions. Thus, it is not possible for a function to exist but for its address to not be present in the VTABLE. Consider the following program, which shows the possibility of a function existing but an address not being located in the VTABLE:
15.12 Write a program to redefine a virtual base class function in the derived class. Also, add a new member in the derived class. Observe the VTABLE.
#include<iostream.h>
#include<conio.h>
class A
{
public:
virtual void joy()
{
cout<<endl<<“In joy of class A”;
}
};
class B: public A
{
public:
void joy() {cout<<endl<<“In joy of class B”;}
void virtual joy2()
{
cout<<endl<<“In joy2 of class B”;
}
};
void main()
{
clrscr();
A *a1,*a2;
A a3;
B b;
a1=&a3;
a2=&b;
a1->joy();
a2->joy();
// a2->joy2(); // joy2 is not member of class A
}
OUTPUT
In joy of class A
In joy of class B
Explanation: In the above program, the base class A contains one virtual function joy(). In the derived class B, the function joy() is redefined and defines a new virtual function joy2(). Figure 15.7 shows VTABLES created for the base and derived class.
Fig. 15.7 VTABLES for base and derived class
a2->joy2();
The above statement will generate an error message. In the above statement, the pointer a2 is treated as only a pointer to the base class object. The function joy2() is not a member of base class A. Hence, it is not allowed to invoke the function joy2() using the pointer object a2. However, an address of the derived class is assigned to the base class pointer, and the compiler has no way to determine that we are working with a derived class object. The compiler avoids calling virtual functions present only in derived classes. In a hierarchical class organization of various levels, if it is essential to invoke a function at any level by using a base class pointer, then the function should be declared virtual in the base class.
Virtual functions permit us to manipulate both base and derived objects using similar member functions with no modifications. Virtual functions can be invoked using a pointer or reference. If we do so, object slicing takes place. The following program takes you to the real thing:
15.13 Write a program to declare reference to object and invoke functions.
#include<constream.h>
#include<iostream.h>
class B
{
int j;
public:
B (int jj) {j=jj;}
virtual void joy()
{
cout<<“ In class B”;
cout<<endl<<“ j=”<<j;
}
};
class D : public B
{
int k;
public:
D (int jj, int kk) : B (jj)
void joy()
{
B::joy();
cout<<“ In class D”;
cout<<endl<<“ k=”<<k;
}
};
void main()
{
clrscr();
B b(3);
D d (4,5);
B &r=d;
cout<<“ Using Object”;
d.joy();
cout<<“ Using Reference”;
r.joy();
}
OUTPUT
Using Object
In class B
j= 4
In class D
k= 5
Using Reference
In class B
j= 4
In class D
k= 5
Explanation: In the above program, a reference object r is created to an object d using the statement B &r = d;. The member function joy() is invoked using r and d objects. The output is similar.
15.14 Write a program to demonstrate object slicing.
#include<iostream.h>
#include<constream.h>
class A
{
public:
int a;
};
class B: public A
{
public:
int b;
B() {a=40; b=30;}
};
void main()
{
clrscr();
A x;
B y;
cout<<“ a=”<<x.a <<“”;
x=y;
cout<<“ Now a=”<<x.a;
}
OUTPUT
a=10 now a=40
Explanation: In the above program, class A has only one data member a and derived class B has one member b. In function main(), objects x and y of classes A and B are declared. The object x has only one member, that is, a and the object y has two members a and b. The statement x = y, that is, the derived class object, is assigned to the base class object. In such an assignment, only base, class part of the derived object is assigned to the base class object. Thus, if an object of a derived class is assigned to a base class object, the compiler allows it. However, it copies only the base class members of the object, and this process is known as object slicing.
It is possible to invoke a virtual function using a constructor. A constructor makes the virtual mechanism illegal. When a virtual function is invoked through a constructor, the base class virtual function will not be called; instead, the member function of a similar class is invoked.
15.15 Write a program to call virtual function through constructor.
#include<iostream.h>
#include<constream.h>
class B
{
int k;
public:
B (int l) {k=l;}
virtual void show() {cout<<endl<<“ k=”<<k;}
};
class D: public B
{
int h;
public:
D (int m, int n) : B (m)
{
h=n;
B *b;
b=this;
b->show();
}
void show()
{
cout<<endl<<“ h=”<<h;
}
};
void main()
{
clrscr();
B b(4);
D d(5,2);
}
OUTPUT
h=2
Explanation: In the above program, the base class B contains a virtual function. In the derived class D, a similar function is redefined. Both the base and derived classes contain a constructor. In the derived class constructor, the base class pointer *b is declared. We know that this pointer contains the address of the object calling the member function. Here, the this pointer holds the address of the object d. The pointer object b invokes the function show(). The derived class show() function is invoked.
Here, the object d is not fully constructed; then, how does it invoke the member function of a similar class? This is possible, because a virtual function call reaches ahead into inheritance.
We have learned how to declare virtual functions. Likewise, destructors can be declared as virtual. The constructor cannot be virtual, as it requires information about the accurate type of the object in order to construct it properly. The virtual destructors are implemented in a similar manner to virtual functions. In constructors and destructors, pecking order (hierarchy) of base and derived classes is constructed. Destructors of derived and base classes are called when a derived class object addressed by the base class pointer is deleted.
For example, a derived class object is constructed using a new operator. The base class pointer object holds the address of the derived object. When the base class pointer is destructed using the delete operator, the destructor of the base and derived class is executed. The following program explains this:
15.16 Write a program to define virtual destructors.
#include<iostream.h>
#include<conio.h>
class B
{
public:
B() {cout<<endl<<“In constructor of class B”;}
virtual ~B() {cout<<endl<<“In destructor of class B”;}
};
class D: public B
{
public:
D() {cout<<endl<<“In constructor of class D”;}
~ D() {cout<<endl<<“In destructor of class D”;}
};
void main()
{
clrscr();
B *p;
p= new D;
delete p;
}
OUTPUT
In constructor of class B
In constructor of class D
In destructor of class D
In destructor of class B
Explanation: In the above program, the destructor of the base class B is declared as virtual. A dynamic object is created, and the address of the nameless object that is created is assigned to pointer p. The new operator allocates the memory required for data members. When the object goes out of scope, it should be deleted, and the same should be performed by the statement delete p;. When the derived class object is pointed by the base class pointer object, in order to invoke the base class destructor, virtual destructors are useful.
When a virtual function is invoked through a non-virtual member function, late binding is performed. When a virtual function is called through the destructor, the redefined function of a similar class is invoked. Consider the following program:
15.17 Write a program to call virtual function using destructors.
#include<iostream.h>
#include<conio.h>
class B
{
public:
~ B() {cout<<endl<<“ in virtual destructor”;}
virtual void joy() {cout<<endl<<“In joy of class B”;}
};
class D : public B
{
public :
~ D()
{
B *p;
p=this;
p->joy();
}
void joy() {cout<<endl<<“ in joy() of class D”;}
};
void main()
{
clrscr();
D X;
}
OUTPUT
in joy() of class D
in virtual destructor
Explanation: In the above program, the destructor of the derived class function joy() is invoked. The member function joy() of the derived class is invoked followed by the virtual destructor.
(A) Answer the following questions
(B) Answer the following by selecting the appropriate option
(C) Attempt the following programs
(D) Find the bugs in the following programs
class B {virtual void display()=0;};
void main() {B d;}
class B
{
public:
virtual void display()
{
cout<<“ no function display defined in this class.”;
}
};
struct D : B {};
void main()
{
D d;
d.display();
}
class B
{
int a,b,c;
public:
B() {a=10, b=20,c=40;}
};
class D : public B {};
void main()
{
B b;
D d;
d=b;
}
class B {};
class D : public B
{
int i,j,k;
public:
D() {i=5; j=10; k=15;}};
void main()
{
B b;
D d;
b=d;
cout<<b.i;
}
class B
{public:
B() {cout<<endl<<“In constructor of class B”;}
virtual ~B() =0;
};
class D : public B
{
public:
D() {cout<<endl <<“In constructor of class D”;}
~D() {cout<<endl<<“In destructor of class D”;}
};
void main()
{
B *p;
p=new D;
delete p;
}