8.1 Virtual Functions:
Polymorphism refers to the property by which objects belonging to
different classes are able to respond to the same message, but in different
forms. An essential requirement of polymorphism is therefore the ability to
refer to, objects without any regard to their classes. This necessitates the
use of a single pointer variable to refer to the objects of different classes.
Here, we use the pointer to base class to refer to all the derived objects.
But, we just discovered that a base pointer, even when it is made to contain
the address of a derived class, always executes the function in the base class.
The compiler simply ignores the contents of the pointer and chooses the member
function that matches the type of the pointer. How do we then achieve
polymorphism? It is achieved using what is known as 'virtual' functions.
Virtual means existing
in effect but not reality. A virtual function then, is one that does not
really exist but nevertheless appears real to some parts of a program. Virtual
functions provide a way for a program to decide: when it is running, what
function to call. Ordinarily such decisions are made at compile time. Virtual
functions take possible greater flexibility in performing the same kind of
action on different kinds of objects. In particular, they allow the use of
functions called from an array of type pointer-to-base that actually holds
pointers to a variety of derived types. Typically a function is declared
virtual in the base class, and other functions with the same name are declared
in derived classes. A pure virtual function has no body in the base class.
When we use
the same function name in both the base and derived class the friction in base
class is declared as virtual using
the keyword virtual preceding its normal declaration. When a function is made
virtual, C++ determines which function to use at run time based on the type of
object pointed to by the base pointer rather than the type of the
pointer, Thus by making the base pointer to point to different objects, we can
execute different versions of the virtual function. Program below illustrates
this point.
#
include<iostream.h>
class Base
{
public:
void display ( ) { cout < < "\n Display
base "; }
virtual void show ( ) { cout < < " \n show
base"; }
};
class
Derived: public Base
{
public:
void display( ) { cout << “\n Display
derived"; }
void show ( )
{ cout << " \n show
derived" ; }
} ;
void main(
)
{
Base B;
Derived D;
Base *bptr; //Declarations
cout < < "\n bptr points to Base \n" ;
bptr = &B;
bptr -> display( ) ; //calls Base version
bptr -> show( ) ; //calls
Base version
cout << "\n\n bptr points to
Derived\n";
bptr = &D;
bptr -> display( ); //calls Base version
bptr -> show( ) ; //calls
Derived version
}
Note that
when bptr is made to point to the
object D, the statement
bptr -> display( );
calls
only the function associated with the Base (i.e. Base :: display( )) whereas
the statement
bptr -> show-6;
calls the Derived version of show( ). This is because the function display( ) has not been made virtual
in the Base class. One important
point is that, we must access virtual functions through the use of a pointer
declared as a pointer to the base class. We can use the object name with the
dot operator the same way as any other member function to call the virtual
functions, but runtime polymorphism is achieved only when a virtual function is
accessed through a pointer to the base class. Consider the program below a
function display( ) is used in all
the classes to display the class contents. Notice that the function display( ) has been declared virtual in
the base class. In the main program we create a heterogeneous list of pointers
of type media as shown below:
media *lst[2] = {&book1 , &tape1};
The base
pointers list[0] and list[l] are initialized with the addresses of objects
book1 and tape1 respectively.
# include
<iostream.h>
# include
<string.h>
class media
{ protected:
char title[50];
float price;
public:
media(char *s, float a)
{
strcpy(title, s);
price = a;
}
virtual void display( ) { } //empty
virtual function;
};
class book:
public media
{
int pages;
public:
book(char *5, float a, int pj ):media(s,a)
{ pages
= p; }
void display ( );
};
class tape:
public media
{ float time;
public:
tape(char * s, float a, float t) :media(s, a)
{ time =
t; }
void display( ) ;
};
void book
:: display( )
{
cout << "\n Title: " << title;
cout << "\n Pages: " << pages;
cout << "\n Price: “ << price;
}
void tape:
: display( )
{
cout << "\n Title: " << title;
cout << "\n play time: " << time
<< "mins";
cout << "\n price: “ << price;
}
void main(
)
{ char * title;
float price, time;
int pages;
cout << "\n ENTER BOOK DETAILS\n";
cout << " Title:"; cin >> title;
cout << " Price: "; cin >> price;
cout << " Pages: "; cin >> pages;
book bookl ( title, price, pages) ;
cout << "\n ENTER TAPE DETAILS\n";
cout << “ Title: "; cin >> title;
cout << " Price: "; cin >> price;
cout << " Play time (mins): "; cin >> time;
tape tapel(title, price, time) ;
media* list [2] ;
list[0] = &bookl;
list[l] = &tapel;
cout << "\n MEDIA DETAILS";
cout << "\n BOOK ";
list[0] -> display( ); //display book details
cout << "\n TAPE ";
list[l] -> display( ) ; //display tape details
}
8.2 Rules for Virtual Functions:
When
virtual functions are created for implementing late binding, we should observe
some basic rules that satisfy the compiler requirements:
- The virtual functions must be members of some class.
- They cannot be static members.
- They are accessed by using object pointers.
- A virtual function can be a friend of another class.
5.
A
virtual function in a base class must be defined, even though it may not
be used.
- The prototypes of the base class version of a virtual function and all the derived class versions must be identical. If two functions with the same name have different prototype, C++ considers them as overloaded functions, and the virtual function mechanism is ignored.
- We cannot have virtual constructors/ but we can have virtual destructors.
- While a base pointer can point to any type of the derived object, the reverse is not true. That is, we cannot use a pointer to a derived class to access an object of the base type.
- When a base pointer points to a derived class, incrementing or decrementing it will not make it to point to the next object of the derived class. It is incremented or decremented only relative to its base type. Therefore, we should not use this method to move the pointer to the next object.
- If a virtual function is defined in the base class, it need not be necessarily redefined in the derived class. In such cases, calls will invoke the base function.
8.3 Virtual Member Functions Accessed With
Pointers:
We can make
use of pointers to access the virtual member function. The program below
illustrates this.
#
include<iostream.h>
class base //base class
{
public:
virtual void show(
{
cout<<”\n base”;
}
};
class der1:
public base //derived class 1
{
public:
void show( )
{ cout<<
“\n Derv1 “; }
};
class der2:
public base //derived class 2
{
public:
void show( )
{ cout<<
“\n Derv2 “; }
};
void main(
)
{
Derv1 dv1; //object
of derived class 1
Derv2 dv2; //object
of derived class 2
ptr =&dv1; // put address of dv1 in
pointer
ptr->show( ); //execute show ( )
ptr =&dv2; // put address of dv2 in
pointer
ptr->show( ); //execute show ( )
}
The member
functions of the derived classes are executed and not the base class. The
function call
ptr -> show( );
executes
different functions, depending on the contents of ptr. The rule is that the compiler selects the function based on
the contents of the pointer ptr.
The compiler
has no problem with the expression
ptr -> show( )
It always
compiles a call to the show( )
function in the base class. But in the program above the compiler doesn't know
what class the contents of ptr may
contain. It could be the address of an object of the Derv1 class or of the Derv2
class. Which version of
draw( ) does the compiler call? In fact the compiler doesn't
know what to do, so it arranges for
the decision to be deferred until the program is running. At run time, when it
is known what class is pointed to by ptr,
the appropriate version of draw will be called. This is called late binding or dynamic binding. (Choosing functions in the normal way, during
compilation, is called early, or static, binding.) Late binding requires
some overhead but provides increased power and flexibility.
8.4 Pure Virtual Functions:
A pure
virtual function is a virtual function with no body. It is a normal practice to
declare a function virtual inside the base class and redefine it in the derived
classes. The function inside the base class is seldom used for performing any
task, It only serves as a placeholder, For example, we have not defined any
object of class media and therefore the function display( ) in the base class
has been defined ‘empty'. Such functions are called "do- nothing"
functions,
A
'do-nothing" function may be defined as follows:
virtual void display( ) = 0;
Such
functions are called pure virtual
functions. A pure virtual function is a function declared in a base class that
has no definition relative to the base class. In such cases, the compiler
requires each derived class to either define the function or redeclare it as a
pure virtual function, Remember that a class containing pure virtual functions
cannot be used to declare any objects of its own. Such classes are called abstract base classes. The main
objective of an abstract base class is to provide some traits, to the derived
classes and to create a base pointer required for achieving runtime
polymorphism.
// Program to demonstrate pure virtual
function
# include
<iostream.h>
class Base
{
public:
virtual void show( ) = 0; // pure virtual function
} ;
class Derv1
: public Base //
derived class 1
{
public:
void show( )
{
cout << "\nDerv1";
}
};
class Derv2
: public Base //derived
class 2
{
public :
void show( )
{ cout << "\nDerv2"; }
};
void main(
)
{
Base*
list[2];
II list of pointers to base class
Derv1 dv1; II object of derived class 1
Derv2 dv2; II object of derived class 2
list[0] =
&dv1; II put address of dv1 in list
list[1] =
&dv2; // put address of dv2 in list
list[0]->show(
); II execute show( ) in both objects
list[1]->show(
);
}
Now the
virtual function is declared as
virtual void
show( ) = 0; II pure virtual function
The equals
sign here has nothing to do with assignment; the value 0 is not assigned to
anything. The = 0 syntax is simply how we tell the compiler that a function
will be pure that is, have no body. You might wonder, if we can remove the body
of the virtual show ( ) function in the base class, why we can't remove the
function altogether. That would be even cleaner, but it doesn't work. Without a
function show() in the base class
statements like
list[0]->show();
would not
be valid, because the pointers in the list[
] array must point to members of class Base. The addresses of the member
functions are stored-in an array of
pointers, and accessed using array elements. This works in just the same
way as using a single pointer.
8.5 Abstract classes:
An abstract class is one that is not used
to create object. It is designed only to act as a base class (to be inherited
by other classes). It is a design concept in program development and provides a
base upon which other classes may be built. It is often defined as one that
will not be used to create any objects, but exists only to act as a base class
of other classes.
8.6 Virtual Base Classes:
Virtual
base classes allow an object derived from multiple bases that themselves share
a common base to inherit just one object of that shared base class.
Consider
the situation with a base class, Parent; two derived classes Child1 and Child2; and a fourth class, Grandchild,
derived from both Child1and Child2.In this arrangement a problem
can arise if a member function in the Grandchild
class wants to access data or functions in the Parent class.
//ambiguous
reference to base class
class
Parent
{
protected:
int basedata;
};
class
Child:public parent
{ };
class
Child2 : public Parent
{ };
class
Grandchild: public Child, public Child2
{
public:
int getdata( )
{ return basedata; } //
ERROR: ambiguous
);
A compiler
error occurs when the getdata ( ) member
function in Grandchild attempts to
access base data in Parent. Because
the Child1 and Child2 classes are derived from Parent, each inherits a copy of Parent; this copy is called a subobject.
Each of the two subobjects contains its own copy of Parent’s data, including basedata.
Now, when Grandchild refers to basedata,
which of the two copies will it access? The situation is ambiguous, and that's
what the compiler reports. The duplication of inherited members due to these multiple
paths can be avoided by making the common base class as virtual base class. To eliminate the ambiguity in the above program,
we make Child1 and Child2 into virtual base classes.
class
Parent
{
protected:
int basedata;
};
class
Child1 : virtual public Parent //
shares copy or Parent
{ };
class
Child2 : virtual public Parent //
shares copy of Parent
{ } ;
class Grandchild:
public Child, public Child2
{
public:
int getdata( )
{ return basedata; } //
OK: only one copy of Parent
};
The use of
the keyword virtual in these two
classes causes them to share a single common subobject of their base class Parent.
Now, since there is only one, copy of basedata,
there is no ambiguity when it is referred to in Grandchild.
8.7 Friend:
The private
members cannot be accessed from outside the class. That is, a non-member
function cannot have an access to the private data of a class. However, there
could be a situation where we would like two classes to share a particular
function. In such situations, C++ allows the common function to be made friendly with both the classes, thereby
allowing the function to have access to the private data of these classes. Such
a function need not be a member of any of these classes.
A friend function can access a class's
private data, even though it is not a member function of the class. This is
useful when one function must have access to two or more unrelated classes and
when an overloaded operator must use, on its left side, a value of a class
other than the one of which it is a member. friends are also used to facilitate functional notation.
As you’ve
seen, C++ controls access to the private portions of a class object. Usually public class methods serve as the
only access, but sometimes this restriction is too rigid to fit particular
programming problems. In such cases, C++ provides another form of access, the friend. Friends come in three varieties:
·
Friend
functions
·
Friend
class
·
Friend
member functions
By making a
function a friend to a class, you allow the function the same access privileges
that a member function of the class has. To make an outside function
"friendly" to a class, we have to simply declare this function as a
friend of the class as shown below:
class ABC
{ ………….
public:
………….
friend
void xyz(void); // declaration
};
The keyword
friend should precede the function
declaration. The function is defined
elsewhere in the program like a normal C++ function. The function definition
does not use either the keyword friend or the scope operator(::). The functions that are declared
with the keyword friend are known as
friend functions. A function can be
declared as a friend in any number of classes. A friend function, although not
a member function, has full access rights to the private members of the class.
A friend
function possesses certain special characteristics:
- It is not in the scope of the class to which it has been declared as friend.
- Since it is not in the scope of the class, it cannot be called using the object of that class. It can be invoked like a normal function without the help of any object.
- Unlike member functions, it cannot access the member names directly and has to use an object name and dot membership operator with each member name. (e.g.A.x). .
- It can be declared either in the public or the private part of a class without affecting its meaning.
- Usually, it has the objects as arguments.
//Program
below illustrates the use of a friend function. ,
# include
<iostream.h>
class
sample
{
int a;
int b;
public:
void setvalue( ) {a = 25; b = 40; }
friend float mean(sample s); //FRIEND declared
} ;
float mean(sample s)
{ return float(s.a + s.b)/2.0; }
void main(
)
{
sample X; //object X
X.setvalue( ) ;
cout << "Mean value = “ << mean(X)
<< "\n";
}
Note that
the friend function accesses the class variables a and b by using the dot
operator and the object passed to it. The function call mean(X) passes the object X
by value to the friend function. Member functions of one class can be friend
functions of another class. In such cases, they are defined using the scope
resolution operator as shown below:
class X
{ …………
…………
int fun1( ); //member
function of X
…………
};
class Y
{
…………
friend int X :: fun1( ); II fun1(
) of X is friend of y
…………
};
The
function funl( ) is a member of
class X and a friend of class Y.
We can also
declare all the member functions of one class as the friend functions of
another class. In such cases, the class is called a friend class. This can be
specified as follows:
class Z
{
…………
friend class X; Il all member functions of X are I
friends to Z
};
Program
below demonstrates how friend functions work as a bridge between the classes.
Note that the function max( ) has
arguments from both XYZ and ABC. When the function max( ) is declared as a friend in XYZ for the first time, the compiler
will not acknowledge the presence of ABC
unless its name is declared in the beginning as
class ABC;
This is
known as 'forward' declaration.
# include
<iostream.h>
class ABC: // Forward declaration
class XYZ
{
int x;
public:
void setvalue(int i)
{ x = i; }
friend void
max(XYZ, ABC) ;
};
class ABC
{
int a;
public:
void setvalue(int i) { a
= i; }
friend void max(XYZ, ABC) ;
} ;
void
max(XYZ m, ABC n ) // Definition of
friend
{
if(m.x >= n.a)
cout << m.x;
else
cout << n.a;
}
main( )
{
ABC abc;
abc.setvalue(10);
XYZ xyz;
xyz.setvalue(20);
max(xyz, abc) ;
}
As pointed
out, a friend function can be called by reference. In this case, local copies
of the objects are not made. Instead, a pointer to the address of the object is
passed and the called function directly works on the actual object used in the
call. This method can be used to alter the values of the private members of a
class. Remember, altering the values of private members is against the basic
principles of data hiding. It should be used only when absolutely necessary.
Program below shows how to use a common friend function to exchange the private
values of two classes. The function is called by reference.
# include
<iostream.h>
class
class_2;
class
class_1
{ int valuel;
public:
void indata(int a)
{ valuel
= a; }
void display(void)
{ cout
<< valuel << "\n"; }
friend void exchange(class_l &, class_2 &) ;
};
class
class_2
{ int value2;
public:
void indata(int a) { value2
= a; }
void display(void) { cout
<< value2 << "\n"; }
friend void exchange(class-l &, class-2 &) ;
} ;
void
exchange(class_l & x, class_2 & y)
{
int temp = x.value1;
x.value1 = y.value2;
y.value2 = temp;
}
void main(
)
{
class_1 C1;
class_2 C2;
C1.indata(100);
C2.indata(200) ;
cout << "Values before exchange"
'<< "\n";
C1.display( ) ;
C2.display( ) ;
exchange(C1, C2) ; //SWAPPING
cout << "Values after exchange"
<<”\n";
C1.display( );
C2.display( );
}
The objects
x and y are aliases of C1 and C2 respectively. The statements
int temp = x.value1
x.value1 = y.value2;
y.value2 = temp;
directly
modify the values of value1 and value2 declared in class_1 and class_2.
friend class: The member function of a class can all be
made friends at the same time when you make the entire class a friend. For
example consider the program below:
# include
<iostream.h>
class alpha
{
private:
int data;
public:
alpha( )
{ data = 99 }
friend class beta; // beta is a friend class
};
class beta
{
public:
void func1(alpha a) { cout
<< “\n data= “<< a.data; }
void func2(alpha a) { cout
<< “\n data= “<< a.data; }
void func3(alpha a) { cout
<< “\n data= “<< a.data; }
};
void main(
)
{
alpha a;
beta b;
b.func1(a);
b.func2(a);
b.func3(a);
}
In class alpha the entire class beta is proclaimed a friend. Now all
the member function of beta can
access the private data of alpha.
Note that in the friend declaration
we specify that beta is a class
using the class keyword:
friend class beta;
8.9 this Pointer:
The this pointer is predefined in member
functions to point to the object of which the function is a member. The this pointer is useful in returning the
object of which the function is a member. The member functions of every object
have access to a sort of magic pointer named this, which points to the object itself. Thus any member function can find out the
address of the object of which it is a member.
#
include<iostream.h>
class where
{
private:
char charray[10];
public:
void reveal( )
{ cout<<”\n My
object’s address is”<<this; }
};
void main(
)
{
where
w1,w2, w3; // make three
objects
w1.reveal(
)
w2.reveal(
)
w3.reveal(
)
}
The main( ) program in this example creates
three objects of type where. It then
asks each object to print its address, using the reveal( ) member function.
This function prints out the value of this pointer.
Exercise:
- Create a class Distance and an overloaded * operator so that two Distance class can be multiplied together. Make it a friend function. You will need a one-argument constructor to convert floating-point values into Distance value. Write a program to test this operator.
- Implement the virtual function using the classes Employee (base class), Adminstaff, Factorystaff (both are derived from base class) and Staff (derived from subclasses). Write a program to display the staff details.