Polymorphism in C/C++ And types of Polymorphism
Table of Contents
Polymorphism:
Polymorphism is another most important feature of object oriented programming. In polymorphism, the member function with the same name are defined in each derived class and also in the base class. Polymorphism is used to keep the interface of base class to its derived classess.
Poly means many and morphism means from. Polymorphism, therefore, is the ability for objects of different classes related by inheritance to response differently to the same function call.
Polymorphism is achieved by means of virtual functions. It is rendered possible by the fact that one pointer to a base class object may also point to any object of its derived class.
Amazon Purchase Links:
*Please Note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!
Pointers to Objects:
The members of a class can be accessed through the pointer to the class. The arrow ( -> ) symbol is used to access them. This symbol is also known as member access operator. It is denoted by a hyphen ( – ) and a greater than sign (>) .
The general syntax to access a member of a class through its pointer is :
P -> class member
Where:
P:
is the pointer to the object.
->:
It is the member access operator. It is used to access the members of the object through the pointer.
Class member:
It is the member of the object.
Example how to use polymorphism in C/C++ programming:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <iostream> using namespace std; class ptr { private: char name[15]; int age; public: void input() { cout<<"enter name :"; cin>>name; cout<<"enter Age :"; cin>>age; } void show() { cout<<"name = "<<name<<endl; cout<<"age = "<<age<<endl; } }; int main() { ptr *p; p -> input(); p -> show(); return 0; } |
In the above program, the “ptr” class is defined and “p” is declared as pointer object. The public member functions of the “ptr” are accessed with the member access operator (->)
If a pointer object is defined to point to a class, then it can also point to any of its subclass. However, even if the pointer points to an object of its derived class, it remains of base class type.
A program may have a base class and several derived classes. When a pointer is declared to point to the base class, it can also point to the classes derived from this base class.
When a base class pointer is used to point to an object of a derived class, the pointer type remains unchanged, its type remains of the base class. When this pointer is used to access a member of a class with the member access operator ( ->), the pointer type determines which actual function will be called. It always accesses the member of the class with which its type matches. Thus it will executes the member function of the base class. This is the default method of execution of a member function through a pointer.
A program example is given below to illustrate this concept. The program has a base class and two derived classes. The base class and its derived classes have their member function and the names of their member functions is same.
Example how to use inheritance in Polymorphism in C/C++ programming:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include <iostream> using namespace std; class bb { public: void ppp() { cout<<"hello, it is the base class"<<endl; } }; class d1: public bb { public: void ppp() { cout<<"it is the first derived class"<<endl; } }; class d2: public bb { public: void ppp() { cout<<"it is the second derived class"<<endl; } }; int main() { bb *p; d1 a; d2 b; p=&a; p-> ppp(); p= &b; p-> ppp(); return 0; } |
OutPut:
1 2 |
hello, it is the base class hello, it is the base class |
In the above program, bb is the base class and d1 and d2 are the derived classes. Both these classes have been derived from the base class bb.
In the main() function, the objects declarations are:
One pointer object “P” of base class bb is declared.
One object “a” of derived class d1 is declared.
One object “b” of derived class d2 is declared.
The statements are executed as given below:
The statement “p=&a;” assigns the address of object bb to the pointer object “p”. when the first statement “p->ppp()” is executed, the member function of the base class is executed. Since the type the pointer matches the type member function of the base class, the base class member function is executed.
Similarly, when the second statement “p->ppp();” is executed after assigning the address of object b of the derived class d2 to “p”, again the member function of the base class is executed.
Early Binding in polymorphism:
The events that take place at the compile time are called early binding. It is also called static binding. It indicates that all information required to call a function is known at the compile time the compiler decides at compile time what method will respond to the message sent to the pointer.
The accessing of member functions of a class through the pointer is an example of early binding. In the above program example, the compiler decides at compile time to always execute the member function of the class whose type matches the type of the pointer irrespective of the contents of the pointer. Thus whatever the object points to during execution of the program, the pointer always accesses the member of the class whose type matches with its type.
Types of polymorphism:
Ad hoc polymorphism
Christopher Strachey chose the term ad hoc polymorphism to refer to polymorphic functions that can be applied to arguments of different types, but that behave differently depending on the type of the argument to which they are applied (also known as function overloading or operator overloading). The term “ad hoc” in this context is not intended to be pejorative; it refers simply to the fact that this type of polymorphism is not a fundamental feature of the type system. In the Pascal / Delphi example below, the Add functions seem to work generically over various types when looking at the invocations, but are considered to be two entirely distinct functions by the compiler for all intents and purposes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
program Adhoc; function Add(x, y : Integer) : Integer; begin Add := x + y end; function Add(s, t : String) : String; begin Add := Concat(s, t) end; begin Writeln(Add(1, 2)); Writeln(Add('Hello, ', 'Mammals!')); end. |
In dynamically typed languages the situation can be more complex as the correct function that needs to be invoked might only be determinable at run time.
Implicit type conversion has also been defined as a form of polymorphism, referred to as “coercion polymorphism
Parametric polymorphism
Parametric polymorphism allows a function or a data type to be written generically, so that it can handle values uniformly without depending on their type. Parametric polymorphism is a way to make a language more expressive while still maintaining full static type-safety.
The concept of parametric polymorphism applies to both data types and functions. A function that can evaluate to or be applied to values of different types is known as a polymorphic function. A data type that can appear to be of a generalized type (e.g. a list with elements of arbitrary type) is designated polymorphic data type like the generalized type from which such specializations are made.
Parametric polymorphism is ubiquitous in functional programming, where it is often simply referred to as “polymorphism”. The following example in Haskell shows a parameterized list data type and two parametrically polymorphic functions on them:
1 2 3 4 5 6 7 8 9 |
data List a = Nil | Cons a (List a) length :: List a -> Integer length Nil = 0 length (Cons x xs) = 1 + length xs map :: (a -> b) -> List a -> List b map f Nil = Nil map f (Cons x xs) = Cons (f x) (map f xs) |
Parametric polymorphism is also available in several object-oriented languages. For instance, templates in C++ and D, or under the name generics in C#, Delphi and Java:
1 2 3 4 5 6 7 8 9 10 11 12 |
class List<T> { class Node<T> { T elem; Node<T> next; } Node<T> head; int length() { ... } } List<B> map(Func<A, B> f, List<A> xs) { ... } |
John C. Reynolds (and later Jean-Yves Girard) formally developed this notion of polymorphism as an extension to lambda calculus (called the polymorphic lambda calculus or System F). Any parametrically polymorphic function is necessarily restricted in what it can do, working on the shape of the data instead of its value, leading to the concept of parametricity.
Subtyping
Some languages employ the idea of subtyping (also called subtype polymorphism or inclusion polymorphism) to restrict the range of types that can be used in a particular case of polymorphism. In these languages, subtyping allows a function to be written to take an object of a certain type T, but also work correctly, if passed an object that belongs to a type S that is a subtype of T (according to the Liskov substitution principle). This type relation is sometimes written S <: T. Conversely, T is said to be a supertype of S—written T :> S. Subtype polymorphism is usually resolved dynamically (see below).
In the following example we make cats and dogs subtypes of animals. The procedure letsHear() accepts an animal, but will also work correctly if a subtype is passed to it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
abstract class Animal { abstract String talk(); } class Cat extends Animal { String talk() { return "Meow!"; } } class Dog extends Animal { String talk() { return "Woof!"; } } static void letsHear(final Animal a) { println(a.talk()); } static void main(String[] args) { letsHear(new Cat()); letsHear(new Dog()); } |
In another example, if Number, Rational, and Integer are types such that Number :> Rational and Number :> Integer, a function written to take a Number will work equally well when passed an Integer or Rational as when passed a Number. The actual type of the object can be hidden from clients into a black box, and accessed via object identity. In fact, if the Number type is abstract, it may not even be possible to get your hands on an object whose most-derived type is Number (see abstract data type, abstract class). This particular kind of type hierarchy is known—especially in the context of the Scheme programming language—as a numerical tower, and usually contains many more types.
Object-oriented programming languages offer subtype polymorphism using subclassing (also known as inheritance). In typical implementations, each class contains what is called a virtual table—a table of functions that implement the polymorphic part of the class interface—and each object contains a pointer to the “vtable” of its class, which is then consulted whenever a polymorphic method is called. This mechanism is an example of:
late binding, because virtual function calls are not bound until the time of invocation;
single dispatch (i.e. single-argument polymorphism), because virtual function calls are bound simply by looking through the vtable provided by the first argument (the this object), so the runtime types of the other arguments are completely irrelevant.
The same goes for most other popular object systems. Some, however, such as Common Lisp Object System, provide multiple dispatch, under which method calls are polymorphic in all arguments.
The interaction between parametric polymorphism and subtyping leads to the concepts of variance and bounded quantification.
Row polymorphism
Row polymorphism is a similar, but distinct concept from subtyping. It deals with structural types. It allows the usage of all values whose types have certain properties, without losing the remaining type information.
Polytypism
A related concept is polytypism (or data type genericity). A polytypic function is more general than polymorphic, and in such a function, “though one can provide fixed ad hoc cases for specific data types, an ad hoc combinator is absent
Static and dynamic polymorphism
Polymorphism can be distinguished by when the implementation is selected: statically (at compile time) or dynamically (at run time, typically via a virtual function). This is known respectively as static dispatch and dynamic dispatch, and the corresponding forms of polymorphism are accordingly called static polymorphism and dynamic polymorphism.
Static polymorphism executes faster, because there is no dynamic dispatch overhead, but requires additional compiler support. Further, static polymorphism allows greater static analysis by compilers (notably for optimization), source code analysis tools, and human readers (programmers). Dynamic polymorphism is more flexible but slower—for example, dynamic polymorphism allows duck typing, and a dynamically linked library may operate on objects without knowing their full type.
Static polymorphism typically occurs in ad hoc polymorphism and parametric polymorphism, whereas dynamic polymorphism is usual for subtype polymorphism. However, it is possible to achieve static polymorphism with subtyping through more sophisticated use of template metaprogramming, namely the curiously recurring template pattern.