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: intptr=newintint* ptr = new int.
      • Freed using the delete operator.
      • Example: deleteptrdelete ptr.

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 a = 5
    • int* ptr = &a (Assigns address of a to the pointer)
    • ptr=10*ptr = 10 (a's value becomes 10)
    • intb=ptrint b = *ptr (b becomes 10)
  • Array of Pointers Syntax: data \text{_} type *var \text{_} name[array \text{_} size]. Example: intprice[3]int *price[3].
  • Access Operators:
    • Dot operator (.): Used to access members of an object directly (e.g., p.display()p.display()).
    • Arrow operator (->): Used to access members of an object through a pointer (e.g., ptrPersondisplay()ptrPerson \rightarrow display()).
    • Equivalence: ptrPersondisplay()ptrPerson \rightarrow display() is equivalent to (ptrPerson).display()(*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,pBase b1, *p
      • Derivedd1Derived d1
      • p = &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;virtual void draw() = 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

FeatureVirtual Base ClassVirtual Function
PurposeTo solve the diamond problem in multiple inheritanceTo achieve runtime polymorphism
UsageUsed in class inheritanceUsed in class member functions
BehaviorEnsures only one instance of the base class is created when multiple derived classes inherit from itAllows derived classes to override the base class function
SyntaxclassDerived:virtualpublicBase;class Derived : virtual public Base {};virtualvoidfunctionName();virtual void functionName();
Historical NoteSubject of exam April/May 2023Subject 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:
    1. Define Student with virtualvoiddisplay()=0;virtual void display() = 0;.
    2. Derive Engineering, Science, and Medical. Override the display() method in each.
    3. In main(), create an array of base class pointers: Studentstudents[3]Student* students[3].
    4. Assign addresses of the derived objects to these pointers.
    5. 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".