12

CHAPTER

Binding, Polymorphism
and Virtual Functions

C
H
A
P
T
E
R

O
U
T
L
I
N
E
—•    12.1 Introduction
—•    12.2 Binding in C++
—•    12.3 Pointer to Derived Class Objects
—•    12.4 Virtual Functions
—•    12.5 Rules for Virtual Functions
—•    12.6 Array of Pointers
—•    12.7 Pure Virtual Functions
—•    12.8 Abstract Classes
—•    12.9 Working of Virtual Functions
—•  12.10 Virtual Functions in Derived Classes
—•  12.11 Object Slicing
—•  12.12 Constructors and Virtual Functions
—•  12.13 Virtual Destructors
—•  12.14 Destructors and Virtual Functions

12.1 INTRODUCTION

This topic is absolutely new and those who are new to object-oriented programming, must study this chapter in detail. An attempt has been made to illustrate every point of this new topic in an easy way and a bit 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 whole new word is created called as polymorphism i.e., various forms. We have learnt overloading of functions and operators. It is also one-type of polymorphism. The information pertaining to various overloaded member functions is to be given to the compiler while compiling. This is called as early binding or static binding. In C++ function can be bound either at compile time or run time. Deciding a function call at compiler time is called compile time or early or static binding. Deciding function call at runtime is called as runtime or late or dynamic binding. Dynamic binding permits suspension of the decision of choosing suitable member functions until run-time. Two types of polymorphism are shown in Figure 12.1

POLYMORPHISM

Polymorphism is the technique in which various forms of a single function can be defined and shared by various objects to perform the operation.

images

Fig.12.1 Polymorphism in C++

12.2 BINDING IN C++

Though C++ is an object-oriented programming language, it is very much inspired by procedural language. C++ supports two types of binding, static or early binding and dynamic or late binding.

(1) Static (Early) Binding

Even though similar function names are used at many places, but during their references their position is indicated explicitly. Their ambiguities are fixed at compile time. Consider the following example:

  class first                    // base class
  {
    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 the same member function display( ). This is not an overloaded function because their prototype is same and they are defined in different classes.

(2) Dynamic (Late) Binding

Dynamic binding of member functions in C++ can be done using the keyword virtual. The member function followed by the virtual keyword is called as 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 above example, base and derived classes have the same 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 believed as virtual, supposing they match the base class function exactly in number and types of parameters sent. If there is no match between these functions, they will be assumed as overloaded functions. The virtual function must be defined in 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.

12.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( );
    second s;
    s.first::display( );
    s.display( );
    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 the same function name. In function main( ), S is an object of derived class second. Hence, in order to invoke the display( ) function of base class, scope access operator is used. When base and derived classes have the same function name then 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 function is called as polymorphism. The virtual keyword plays an important role in this process. Before introducing virtual functions let us have a look at the program given below. The following program shows what happens when functions are not declared virtual. This is an example of early binding.

12.2 Write a program to invoke member functions of base and derived classes 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;
  second s;
  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 base class and s is an object of derived class. The pointer *p is an object pointer of base class. Address of base class object is assigned to pointer p and display( ) function is called. Again, address of derived class is assigned to pointer p and display( ) function is called. Both times display( ) function of base class is executed. This is because second call, p contains address of object of derived class, but the display( ) function of base class replaces the existence of display( ) function of derived class. In order to execute various forms of the same function defined in base and derived classes, run-time binding is necessary and it can be achieved using virtual keyword.

12.3 POINTER TO DERIVED CLASS OBJECTS

Inheritance provides hierarchical organization of classes. It also provides 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 point base or derived classes. Pointers to object of base class are type compatible with pointers to object of derived class. A base class pointer can also point to objects of base and derived classes. In other words, a pointer to base class object can point to objects of derived classes whereas a pointer to derived class object cannot point to objects of base class object as shown in Figure 12.2.

images

Fig.12.2 Type-compatibility of base and derived class pointers

From Figure 12.2 it is clear that a base class pointer can point to objects of base and derived classes whereas a derived class pointer cannot point to objects of base class.

12.3 Write a program to access members of base and derived classes 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. 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 class W. The statement b = new W (20) creates a nameless object and returns its address to 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 base class pointer b. Here, note that the object b is pointer object of base class and it is initialized with address of derived class object. Again, pointer b invokes the function show( ) of base class and not the function of class X (derived class.). To invoke the function of derived class X, following statement is used:

((X*)b)->show( );

In the above statement, typecasting (upcasting) is used. The upcasting forces the objects of class W to behave as if they were objects of class X. This time the function show( ) of class X (derived class) is invoked. Obtaining the address of a derived class object, and treating it as the address of the 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 pointer object d of derived class X and assigns address of x to it. The pointer object d invokes the derived class function show( ). Figure 12.3 shows pictorial representation of the above explanation.

Here, pointer of class W can point to its own class as well as its derived class. The pointer of derived class X can point to its own class but cannot to its base class.

images

Fig.12.3 Type-compatibility of base and derived class pointers

12.4 VIRTUAL FUNCTIONS

Virtual functions of base classes must be redefined in the derived classes. The programmer can define a virtual function in a base class and can 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 the same 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 objects is available. With virtual functions we cannot alter the function type. If two functions with the same name have different arguments, C++ compiler considers them as different, and the virtual function mechanism is dropped.

12.5 RULES FOR VIRTUAL FUNCTIONS

(1) The virtual functions should not be static and must be member of a class.

(2) A virtual function may be declared as friend for another class. Object pointer can access virtual functions.

(3) Constructors cannot be declared as virtual, but destructors can be declared as virtual.

(4) The virtual function must be defined in public section of the class. It is also possible to define the virtual function outside the class. In such a case, the declaration is done inside the class and definition is done outside the class. The virtual keyword is used in the declaration and not in function declarator.

(5) It is also possible to return a value from virtual function like other functions.

(6) The prototype of virtual functions in base and derived classes should be exactly the same. In case of mismatch, the compiler neglects the virtual function mechanism and treats them as overloaded functions.

(7) Arithmetic operation cannot be used with base class pointer.

(8) If a base class contains virtual function and if the same function is not redefined in the derived classes in that case the base class function is invoked.

(9) The operator keyword used for operator overloading also supports virtual mechanism.

12.4 Write a program to declare virtual function and execute the same function defined in base and derived classes.

# 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( );
  first f,*p;
  second s;
  p=&f;
  p->display( );
  p=&s;
  p->display( );
    
  return 0;
  }

OUTPUT

b = 10
d = 20

Explanation: The above program is the same as the previous one. The only difference is that the 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 runtime binding. In first call, the display( ) function of base class is executed and in second call after assigning address of derived class to pointer p, display( ) function of derived class is executed.

12.5 Write a program to use pointer for both base and derived classes 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( )
{
  clrscr( );
  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 the member functions with 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 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 base class are preceded by the keyword virtual. If the virtual keyword is removed, both the function calls execute the member functions of base class. The member functions for derived classes are not executed though the pointer has the address of the derived class. If the virtual keyword is not removed, the first function calls, executes the member function of base class and later derived class.

12.6 ARRAY OF POINTERS

Polymorphism refers to late or dynamic binding i.e., selection of an entity is decided at run-time. In class hierarchy, same method names can be defined that perform different tasks, and then the selection of appropriate method is done using dynamic binding. Dynamic binding is associated with object pointers. Thus, address of different objects can be stored in an array to invoke function dynamically. The following program explains this concept.

12.6 Write a program to create an 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;
    B b;
    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 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 base class is declared virtual. An array of pointer *pa is declared and it is initialized with addresses of base and derived class objects i.e., a, b, c, d and e. Using for loop and array, each object invokes function show( ). If the base class function show( ) is non-virtual then every time function show( ) of base class is executed. Figure 12.4 illustrates this concept more clearly.

images

Fig.12.4 Early and late binding of functions

12.7 PURE VIRTUAL FUNCTIONS

In practical applications, the member functions of base classes is rarely used for doing any operation; such functions are called as do-nothing functions, dummy functions, or pure virtual functions. The do-nothing function or pure functions are always virtual functions. Normally pure virtual functions are defined with null-body. This is so because derived classes should be able to override them. Any normal function cannot be declared as pure function. After declaration of pure function in a class, the class becomes abstract class. It cannot be used to declare any object. Any attempt to declare an object will result in an 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, the function display( ) is 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 pure virtual function and it will not have a definition.

A pure virtual function declared in base class cannot be used for any operation. The class containing 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 object from 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 pure abstract classes are required to redeclare the pure virtual function. All other derived classes without pure virtual functions are called as concrete classes. The concrete classes can be used to create objects. A pure virtual function is like unfilled container, the derived class made-up to fill.

12.7 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 as pure function. The pointer object *p holds address of objects of derived classes 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 remark of the above program, the program is terminated with an error “abnormal program termination.”

12.8 ABSTRACT CLASSES

Abstract classes are like skeleton upon which new classes are designed to assemble well-designed class hierarchy. The set of well-tested abstract classes can be used and the programmer only extends them. Abstract classes containing virtual function can be used as 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 new base classes.

The development of such software can be demonstrated by creating a header file. The file abstract. h is an example of file containing abstract base class used for debugging purpose. Contents of file abstract.h is as follows:

Contents of abstract.h header file

   # include <iostream.h>
   struct debug
   {
      virtual void show ( )
      {
         cout<<"
 No function show ( ) defined for this class";
      }
   };

12.8 Write a 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 structure debug contains virtual function show( ). This file is # include in the above program. Classes A and B are derived from class debug which is defined in header file abstract.h. In function, a and b are objects of class A and B respectively. The statement a. show( ) invokes the function show( ) and value of a is displayed. The object b also invokes the function show( ) . However, the class B does not have function show( ). Hence, function show( ) of abstract base class is executed which displays a warning message.

The traditional languages do not provide such facility. An abstract class develops into a dominant and powerful interface when software system undergoes various changes. It is essential to confirm that the debugging interface is accurately constructed. If changes are made in actual project, it is compulsory to add appropriate methods to abstract base classes. In case the programmer needs to define a function warn( ) to the class debug, then the header file can be updated. The contents of file would be

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 abstract classes, following points must be followed:

(1) Do not declare objects of abstract class type.

(2) An abstract class can be used as a base class.

(3) The derived class should not have pure virtual functions. Objects of derived classes can be declared.

12.9 WORKING OF VIRTUAL FUNCTIONS

Before learning mechanisms of virtual functions let's revise few points related to virtual functions.

(1) Binding means link between a function call and the real function that is executed when function is called.

(2) When compiler knows which function to call before execution, it is known as early binding.

(3) Dynamic binding means that the actual function invoked at run time depends on the address stored in the pointer. In this binding, link between function call and actual function is made during program execution.

(4) The keyword virtual prevents compiler to perform early binding. Binding is postponed until program execution.

The programs which follow next illustrate the working of virtual functions step by step.

12.9 Write a program to define virtual, non-virtual functions and determine 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 object of class B that contains one integer data element is two and finally the size of object of class C is displayed one even if class C has no data element.

The function show( ) of class A is prefixed by virtual keyword. Without 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 addition of data member int (2 bytes) and void pointer (2 bytes). When a function is declared as virtual, compiler inserts void pointer. In class C, though it has no object, the size of object displayed is 1 and this is because the compiler attempts the size of object z not to be zero since every object should have an individual address. Hence, minimum size one is considered. The minimum non-zero positive integer is one.

To perform late binding, the compiler establishes VTABLE (virtual table) for every class and its derived classes having virtual functions. 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 address of base class function.

When objects of base or derived classes are created, a void pointer is inserted in the VTABLE called VPTR (vpointer). The VPTR points to VTABLE. When a virtual function is invoked using base class pointer, the compiler speedily puts code to obtain the VPTR and searches for the address of function in VTABLE. In this way, appropriate function is invoked and dynamic binding takes place.

The VPTR should be initialized with beginning address of the apposite VTABLE. When the VPTR is initialized with apposite VTABLE, the type of object can be determined by itself. However, it is useless if it is applied at the point when a virtual function is invoked.

Assume that a base class pointer object points to the object of derived class object. If function is invoked using the base class pointer, the compiler makes different code to accomplish the function call. The compiler begins from base class pointer. The base class pointer holds address of derived class object. With the aid of this address, the VPTR of derived class is obtained. Via VPTR, the VTABLE of derived class is obtained. In the VTABLE, the address of the function being invoked is acquired and function is called. All the above process is handled by the compiler and the user need not worry about it.

The program given next makes the concept clearer.

12.10 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;
  e. area( );
  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 12.5.

images

Fig.12.5 Base and derived classes

images

Fig.12.6 VPTR and VTABLES

As discussed before, a VTABLE is formed for each class having virtual function and for the derived class of the same class. The VTABLE is formed for the classes: shop, shopA, shopB, and shopC. These VTABLES hold address of virtual function. Also, the compiler would place a VPTR that points to the particular VTABLE as shown in Figure 12.6.

The class shopC is without function rent( ). Therefore, the VTABLE holds address of 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 address of 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 functions in the previous page, first two functions, area( ) and rent( ) are virtual and period( ) is non-virtual function. Though address of base class object or derived class object is stored in the base class pointer, the function of base class is invoked because period( ) is 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 function area( ) . The execution of these functions depends on address stored in the base class pointer. For example, p contains address of object a, the functions of class shopA are invoked. Similarly, storing addresses of object 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 address of object s (base class). Therefore, when 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 address of function area( ) for class shop is accessed. With the aid of 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; address of derived class object is stored in the base class pointer. When function area( ) is invoked, the VPTR of object a is used to obtain the VTABLE of class shopA. From this VTABLE address ofshopA:: 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 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 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 VTABLES are not created. The functions are called as usual.

C++ places addresses of the virtual member functions in the virtual table. When call to these member functions is made, 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, they provide better flexibility.

12.10 VIRTUAL FUNCTIONS IN DERIVED CLASSES

We know that when functions are declared virtual in base classes, it is mandatory to redefine virtual functions in the derived class. The compiler creates VTABLES for the derived classes and stores address of function in it. In case the virtual function is not redefined in the derived class, the VTABLE of derived class contains address of base class virtual function. Thus, the VTABLE contains address of all functions. Thus, to some extent it is not possible that a function existing has its address not present in the VTABLE. Consider the following program. It shows a possibility when a function exists but address is not located in the VTABLE. Figure 12.7 shows VTABLE for base and derived classes.

12.11 Write a program to redefine a virtual base class function in the derived class. Also, add 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 12.7 shows VTABLES created for base and derived classes.

   a2->joy2( );

The above statement will generate an error message. In the above statement, pointer a2 is treated as only pointer to 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 derived class is assigned to base class pointer, and the compiler in no way can determine that we are working with derived class objects. The compiler avoids calls to virtual functions present only in derived classes. In hierarchical class organization of various levels, if it is essential to invoke a function at any level by using base class pointer, then the function should be declared virtual in the base class.

images

Fig.12.7 VTABLES for base and derived classes

12.11 OBJECT SLICING

Virtual functions permit us to manipulate both base and derived objects using same member functions with no modifications. Virtual function can be invoked using pointer or reference. If we do so, object slicing takes place. The following program takes you to the real thing.

12.12 Write a program to declare reference to object and invoke function.

# 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)
{ k=kk; }
  
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 object d using the statement B &r=d. The member function joy( ) is invoked using r and d objects. The output is same.

12.13 Write a program to demonstrate object slicing.

# include <iostream.h>
# include <constream.h>
 
class A
{
  public :
  int a;
 
  A ( ) { a=10; } };
 
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( ) object x and y of class A and B are declared. The object x has only one member i.e., a and the object y has two members a and b. The statement x=y i.e., the derived class object is assigned to base class object. In such assignment part of derived object is assigned to 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.

12.12 CONSTRUCTORS AND VIRTUAL FUNCTIONS

It is possible to invoke a virtual function using a constructor. Constructor makes the virtual mechanism illegal. When a virtual function is invoked through a constructor, the base class virtual function will not be called and instead of this the member function of the same class is invoked.

12.14 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 the same function is redefined. Both the base and derived classes contain constructors. In the derived class constructor, a base class pointer *b is declared. We know that this pointer contains address of object calling the member function. Here, the this pointer holds address of object d. The pointer object b invokes function show( ). The derived class show( ) function is invoked.

Here, though the object d is not fully constructed it still invokes the member function of the same class. This is possible because a virtual function call reaches ahead into inheritance.

12.13 VIRTUAL DESTRUCTORS

We know how virtual functions are declared. Likewise, destructors can be declared as virtual. The constructor cannot be virtual, since it requires information about the accurate type of the object in order to construct it properly. The virtual destructors are implemented in the way like 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 new operator. The base class pointer object holds the address of the derived object. When the base class pointer is destroyed using delete operator, the destructor of base and derived classes is executed. The following program explains this:

12.15 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 nameless object created is assigned to pointer p. The new operator allocates memory required for data members. When object goes out of scope it must be deleted and the same performed by the statement delete p. When derived class object is pointed by the base class pointer object, in order to invoke base class destructor, virtual destructors are useful.

12.14 DESTRUCTORS AND VIRTUAL FUNCTIONS

When a virtual function is invoked through a non-virtual member function, late binding is performed. When call to virtual function is made through the destructor, the redefined function of the same class is invoked. Consider the following program:

12.16 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, in destructor of the derived class function joy( ) is invoked. The member function joy( ) of derived class is invoked followed by virtual destructor.

SUMMARY

(1) 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 whole new word is created called as polymorphism i.e., various forms.

(2) The information pertaining to various overloaded member functions is to be given to the compiler while compiling. This is called as early binding or static binding. Deciding function call at run-time is called as run-time or late or dynamic binding. Dynamic binding permits to suspend the decision of choosing suitable member functions until run-time.

(3) Pointers to objects of base classes are type compatible with pointers to objects of derived classes. Reverse in not possible.

(4) Virtual functions of base class must be redefined in the derived classes. The programmer can define a virtual function in a base class, and then can use the same function name in any derived class.

(5) Address of different objects can be stored in an array to invoke function dynamically.

(6) In practical applications, the member function of the base class is rarely used for doing any operation; such functions are called as do-nothing functions, dummy functions, or pure virtual functions.

(7) All other derived classes without pure virtual functions are called as concrete classes.

(8) Abstract classes are like skeleton upon which new classes are designed to assemble well-designed class hierarchy. They are not used for object declaration.

(9) Virtual function can be invoked using pointer or reference.

(10) 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, this process is known as object slicing.

(11) It is possible to invoke a virtual function using a constructor. Constructor makes illegal the virtual mechanism.

(12) We learned how to declare virtual functions. Likewise, destructors can be declared as virtual. The constructor cannot be virtual. The virtual destructors are implemented in the same way like virtual functions. Destructors of derived and base classes are called when a derived class object addressed by the base class pointer is deleted.

EXERCISES

[A] Answer the following questions.

(1) What is polymorphism?

(2) Explain compile time and run-time binding.

(3) Explain the use of virtual keyword.

(4) What are pure functions? How are they declared?

(5) Is it possible to declare an object of the class that contains pure function?

(6) What is the difference between virtual function and virtual classes?

(7) What are virtual destructors?

(8) How C++ compiler accomplishes dynamic binding?

(9) Where do we use virtual functions? Give its applications.

(10) What is early binding and late binding?

(11) Explain object slicing.

(12) Explain virtual destructors.

(13) What are abstract classes? How can they be used for debugging a program?

(14) What are VPTR and VTABLE? Explain in detail.

(15) Describe rules for declaring virtual functions.

(16) What is the difference between base class pointer and derived class pointer?

[B] Answer the following by selecting the appropriate option.

(1) Consider the statement virtual void display( ) = 0. The display( ) function is

(a) pure virtual function

(b) pure member function

(c) normal function

(d) all of the above

(2) The do-nothing function is nothing but

(a) pure virtual function

(b) pure member function

(c) both (a) and (b)

(d) none of the above

(3) Static binding is done at the time of

(a) compilation of the program

(b) at run-time

(c) both (a) and (b)

(d) none of the above

(4) Dynamic binding is done using the keyword

(a) virtual

(b) inline

(c) static

(d) void

(5) The virtual keyword solves the

(a) ambiguity in base and derived classes

(b) ambiguity in derived classes

(c) ambiguity in base classes

(d) none of the above

(6) B is a base class object and D is derived class object. The statement B=D

(a) copies all elements of object d to object b

(b) copies only base portion of object d to b

(c) copies only derived portion of object D to B

(d) none of the above

(7) When a base class is not used for object declaration it is called as

(a) abstract class

(b) container class

(c) concrete class

(d) derived class

(8) The derived class without pure virtual function is called as

(a) concrete class

(b) abstract class

(c) container class

(d) derived class

(9) A pointer to base class object can hold address of

(a) only derived class object

(b) only base class object

(c) address of base class object and its derived class object

(d) none of the above

[C] Attempt the following programs.

(1) Write a program to declare a function show( ) in base and derived class. Display message through the function to know name of the class whose member function is executed. Use late binding concept using virtual keyword.

(2) Write a program to define class A, B and C. The class C is derived from A and B. Define count( ) member function in all the classes as virtual. Count number of objects created.

(3) Write a program to declare matrix class which has data member integer array as 3 X 3. Derive class matrix A from class matrix and matrix B from matrix A. All these classes should have a function show( ) to display the contents. Read and display the elements of all three matrices.

(4) Write a program to declare reference object. Invoke member functions of the class using reference object.

(5) Write a program to demonstrate object slicing.

(6) Write a program to demonstrate use of abstract classes.

(7) Write a program to redefine a virtual base class function in the derived class. Also, add new member in the derived class. Observe the VTABLE.

(8) Write a program to define virtual, non-virtual functions and determine size of the objects.

(9) Write a program to invoke member functions of base and derived classes using pointer of base class.

(10) Write a program to access members of base and derived classes using pointer objects of both classes.

[D] Find the bugs in the following programs.

(1)

      class B { virtual void display ( )=0; };
      void main ( ) { B d; }

(2)

      class B

      {
        public:
        virtual void display ( )
      { cout <<"
 no function display defined in this class."; }
      };

      struct D : B { };

      void main( )
      {
        D d;
        d.display( );
      }
      

(3)

      
      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;
      }
      

(4)

      
      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;
      }
      

(5)

      
      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;
      }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.157.6