C++ Polymorphism, Virtual Functions, and Abstract Classes Study Notes
Introduction to Polymorphism
- Etymology: The word "Polymorphism" is derived from two Greek words:
- Poly: Meaning "many".
- Morph: Meaning "forms".
- Definition: Polymorphism is the ability of an object or a function to take more than one form.
- Functional Concept: It refers to processing objects differently based on their data type. Polymorphism allows functions or methods to behave differently based on the object that is calling or invoking them.
- Common Examples:
- Function overloading.
- Operator overloading.
- Constructor overloading.
- Function overriding.
Types of Polymorphism
- Compile-time (Static) Polymorphism:
- Definition: Refers to forms of polymorphism that are resolved by the compiler at compile time.
- Aliases: Also known as early binding or static binding.
- Mechanism: The function to be invoked is determined at compile time.
- Implementation: It is achieved using function overloading and operator overloading.
- Run-time (Dynamic) Polymorphism:
- Definition: Refers to forms of polymorphism that are resolved at runtime.
- Aliases: Also known as late binding or dynamic binding.
- Mechanism: The function to be invoked is determined at runtime based on the object type that invokes the function.
- Implementation: It is achieved using function/method overriding, which is implemented through virtual functions.
Comparative Analysis: Compile-Time vs. Run-Time Polymorphism
- Resolution Time:
- In Compile-time Polymorphism, the function call is resolved at compile time.
- In Run-time Polymorphism, the function call is resolved at run time.
- Binding Type:
- Compile-time is known as Static binding or Early binding.
- Run-time is known as Dynamic binding or Late binding.
- Achievement Method:
- Compile-time is achieved by function/method overloading or operator overloading.
- Run-time is achieved by function overriding using virtual functions and pointers.
- Execution Speed:
- Compile-time provides fast execution because the method to be executed is known early.
- Run-time provides relatively slower execution because the method identification occurs during runtime.
- Flexibility:
- Compile-time is less flexible as all binding executes at compile time.
- Run-time is more flexible as variables and types are handled at run time.
- Inheritance Requirement:
- Inheritance is not involved in Compile-time polymorphism.
- Inheritance is strictly involved in Run-time polymorphism.
Static and Dynamic Binding
- Binding Definition: Binding refers to the process of associating a function call with the function definition.
- Static (Compile-time) Binding:
- Occurs when the function to be invoked is determined at compile time.
- The compiler knows exactly which function is called before execution.
- Characteristics: Faster but less flexible.
- Dynamic (Run-time) Binding:
- Occurs when the function to be invoked is determined at runtime.
- The compiler does not know which functions to call until the program is running.
- Characteristics: Slower but more flexible.
Memory Allocation
- Definition: Memory allocation is the process of reserving a portion of memory for program use. It is a fundamental aspect of programming for storing and manipulating data.
- Static Memory Allocation:
- Memory is allocated at compile time.
- It has a fixed size.
- Done by the compiler automatically (implicitly).
- Allocation and freeing of memory are done implicitly.
- No explicit management is required by the programmer.
- Dynamic Memory Allocation:
- Memory is allocated at runtime.
- Provides flexibility in memory usage.
- Done explicitly by the programmer who requests memory from the system.
- The system returns the starting address of the allocated memory.
- Memory must be explicitly freed when done to avoid memory leaks.
- Implementation in C++:
- Allocated using the
new operator followed by the data type specifier. - Example:
int∗ptr=newint. - Freed using the
delete operator. - Example:
deleteptr.
Pointers and Object Access
- Pointer Basics:
- A variable that stores the memory address as its value.
- Created using the
* operator. - Pointers "point to" the variable whose address they store.
- Accessing the variable via a pointer is done using the dereference operator
*.
- Code Example for Pointers:
inta=5int* ptr = &a (Assigns address of a to the pointer)∗ptr=10 (a's value becomes 10)intb=∗ptr (b becomes 10)
- Array of Pointers Syntax:
data \text{_} type *var \text{_} name[array \text{_} size]. Example: int∗price[3]. - Access Operators:
- Dot operator (.): Used to access members of an object directly (e.g.,
p.display()). - Arrow operator (->): Used to access members of an object through a pointer (e.g.,
ptrPerson→display()). - Equivalence:
ptrPerson→display() is equivalent to (∗ptrPerson).display().
Pointer to Object
- Definition: A variable that stores the memory address of an object.
- Analogy:
- An object is like a book.
- A pointer is like a bookmark.
- The bookmark does not contain the book but tells you the location of the book.
- Static vs. Dynamic allocation for pointers to objects:
- Static Allocation Example:
cpp
Person p; // Static allocation
p.name = "Ram";
Person* ptrPerson = &p; // Pointer to static object
ptrPerson->display();
- Dynamic Allocation Example:
cpp
Person* ptrPerson = new Person; // Heap allocation
ptrPerson->name = "Ram";
ptrPerson->display();
delete ptrPerson; // Manual deallocation
Pointers to Derived Classes
- Definition: A pointer that holds the address of an object of a derived class. This is essential for dynamic binding.
- Base Class Pointer Rules:
- A base class pointer can point to an object of the base class OR any derived class of that base class.
- Limitation: A base class pointer can only access members inherited from the base class. It cannot access members that belong uniquely to the derived class.
- Validity Example:
Baseb1,∗pDerivedd1p = &b1 (Valid)p = &d1 (Valid)
- Derived Class Pointer Rules:
- A derived class pointer can only point to objects of the derived class.
- It cannot point to base class objects or objects of other derived lineages.
- It can access specific derived members as well as inherited members.
- Member Hiding/Shadowing: If a derived class member has the same name as a base class member, accessing that member through a base class pointer will always access the base class version, unless virtual functions are used.
Virtual Functions
- Definition: A member function in a base class declared using the
virtual keyword that is intended to be overridden in derived classes. - Literal Meaning: "Virtual" means existing in appearance but not in reality.
- Mechanism: When a function is virtual, C++ determines which function to use at runtime based on the type of object pointed to by the base pointer, rather than the type of the pointer itself.
- Purpose:
- Achieve runtime polymorphism.
- Allow derived classes to provide specific implementations of functions defined in the base class.
- Usage Rule: Run-time polymorphism is only achieved when the virtual function is accessed through a pointer of the base class type.
Function Overriding
- Definition: Occurs when the base class and derived class have member functions with the exactly the same name, same return-type, and same argument list.
- Purpose: Allows a derived class to offer specialized behavior for a method already provided by the base class.
- Necessity: Needed when a derived class function must perform added or different tasks compared to the base class function.
Pure Virtual Functions and Abstract Classes
- Pure Virtual Function (Abstract Function):
- A special virtual function that has no body definition.
- Acts as a placeholder meant for redefinition by derived classes.
- Syntax: Initialized with
= 0. Example: virtualvoiddraw()=0;. - Requirement: Must be overridden by derived classes. If not overridden, the derived class also becomes abstract.
- Utility: Useful when the base class cannot provide a meaningful implementation but must enforce a specific interface for all children.
- Abstract Class:
- A class that cannot be used to create objects (cannot be instantiated).
- Designed strictly to act as a base class.
- A class is considered abstract if it contains at least one pure virtual function.
- Capabilities: While it cannot be instantiated, pointers and references of the abstract class type can be created to support polymorphism.
Comparison: Virtual Base Class vs. Virtual Function
| Feature | Virtual Base Class | Virtual Function |
|---|
| Purpose | To solve the diamond problem in multiple inheritance | To achieve runtime polymorphism |
| Usage | Used in class inheritance | Used in class member functions |
| Behavior | Ensures only one instance of the base class is created when multiple derived classes inherit from it | Allows derived classes to override the base class function |
| Syntax | classDerived:virtualpublicBase; | virtualvoidfunctionName(); |
| Historical Note | Subject of exam April/May 2023 | Subject of exam April/May 2023 |
Practical Implementation: Student System Example
- Problem Statement (December 2018): Create an abstract class
Student and derived classes Engineering, Science, and Medical. Use an array of base class pointers to call members. - Implementation Logic:
- Define
Student with virtualvoiddisplay()=0;. - Derive
Engineering, Science, and Medical. Override the display() method in each. - In
main(), create an array of base class pointers: Student∗students[3]. - Assign addresses of the derived objects to these pointers.
- Iterate through the array to call the overriden methods.
- Example Code Result:
students[0] = ŋ results in "I am an Engineering student".students[1] = &sci; results in "I am a Science student".students[2] = &med; results in "I am a Medical student".