ScholarQuill logoScholarQuillUniversity Notes
  • Notes
  • Past Papers
  • Blogs
  • Todo
Login
ScholarQuill logoScholarQuillUniversity Notes
Login
NotesPast PapersBlogsTodo
More
SubjectsDiscussionCGPA CalculatorGPA CalculatorStudent PortalCourse Outline
About
About usPrivacy PolicyReportContact
Notes
Past Papers
Blogs
Todo
Analytics
    Current Subject
    🧩
    Object Oriented Programming
    CC-211
    Progress0 / 24 topics
    Topics
    1. Object-Oriented Design: History and Advantages2. Object-Oriented Programming: Terminology and Features3. Classes and Objects4. Data Encapsulation5. Constructors and Destructors6. Access Modifiers7. Const vs Non-Const Functions8. Static Data Members and Functions9. Function Overloading10. Operator Overloading11. Identification of Classes and Their Relationships12. Composition13. Aggregation14. Inheritance15. Multiple Inheritances16. Polymorphism17. Abstract Classes18. Interfaces19. Generic Programming Concepts20. Function Templates21. Class Templates22. Standard Template Library23. Object Streams: Data and Object Serialization24. Exception Handling
    CC-211›Polymorphism
    Object Oriented ProgrammingTopic 16 of 24

    Polymorphism

    8 minread
    1,398words
    Intermediatelevel

    Polymorphism in Object-Oriented Programming (OOP)

    Polymorphism is one of the core concepts in object-oriented programming (OOP) that allows objects of different classes to be treated as objects of a common base class. The term polymorphism comes from the Greek words poly (many) and morph (form), meaning many forms.

    Polymorphism allows methods to have the same name but behave differently based on the type of the object calling them. It enables the use of a single interface for different underlying forms of data or classes.

    There are two main types of polymorphism in C++:

    1. Compile-time Polymorphism (or Static Polymorphism)
    2. Runtime Polymorphism (or Dynamic Polymorphism)

    Types of Polymorphism in C++

    1. Compile-time Polymorphism (Static Polymorphism)

    This type of polymorphism is resolved at compile time. The most common examples of compile-time polymorphism are:

    • Function Overloading
    • Operator Overloading
    Function Overloading

    In function overloading, multiple functions with the same name can exist in the same scope, but they must differ in the number or type of their parameters. The compiler determines which function to call based on the function signature.

    Example of Function Overloading:
    #include <iostream>
    using namespace std;
    
    class Printer {
    public:
        void print(int i) {
            cout << "Printing an integer: " << i << endl;
        }
    
        void print(double d) {
            cout << "Printing a double: " << d << endl;
        }
    
        void print(string str) {
            cout << "Printing a string: " << str << endl;
        }
    };
    
    int main() {
        Printer printer;
        printer.print(10);       // Calls print(int)
        printer.print(3.14);     // Calls print(double)
        printer.print("Hello");  // Calls print(string)
    
        return 0;
    }
    

    Explanation:

    In the example, the print function is overloaded to accept different parameter types (integer, double, and string). The appropriate print function is chosen based on the type of argument passed to it. The decision about which function to call is made at compile-time, hence it is compile-time polymorphism.

    Operator Overloading

    In operator overloading, we redefine the meaning of an operator (like +, -, *, etc.) for user-defined types (like classes). This allows us to perform operations on objects of custom classes just as we would on built-in types.

    Example of Operator Overloading:
    #include <iostream>
    using namespace std;
    
    class Complex {
    private:
        float real;
        float imag;
    
    public:
        Complex() : real(0), imag(0) {}
    
        Complex(float r, float i) : real(r), imag(i) {}
    
        // Overloading the '+' operator
        Complex operator+(const Complex& obj) {
            Complex temp;
            temp.real = real + obj.real;
            temp.imag = imag + obj.imag;
            return temp;
        }
    
        void display() {
            cout << real << " + " << imag << "i" << endl;
        }
    };
    
    int main() {
        Complex c1(3.5, 2.5), c2(1.5, 4.5);
        Complex c3 = c1 + c2;  // Uses overloaded '+' operator
        c3.display();           // Displays the result of c1 + c2
    
        return 0;
    }
    

    Explanation:

    Here, the + operator is overloaded for the Complex class to add two complex numbers. The operator + now performs addition on the real and imag parts of two Complex objects. Since the operator is overloaded at compile-time, this is an example of compile-time polymorphism.


    2. Runtime Polymorphism (Dynamic Polymorphism)

    Runtime polymorphism is resolved at runtime. It allows us to invoke methods of derived classes through a base class pointer or reference. It is achieved through virtual functions and function overriding.

    Function Overriding

    Function overriding occurs when a derived class provides a specific implementation of a method that is already defined in the base class. The function in the base class is marked as virtual to allow dynamic binding at runtime.

    Example of Runtime Polymorphism:
    #include <iostream>
    using namespace std;
    
    class Animal {
    public:
        // Virtual function
        virtual void sound() {
            cout << "Animal makes a sound" << endl;
        }
    };
    
    class Dog : public Animal {
    public:
        // Overriding the base class function
        void sound() override {
            cout << "Dog barks" << endl;
        }
    };
    
    class Cat : public Animal {
    public:
        // Overriding the base class function
        void sound() override {
            cout << "Cat meows" << endl;
        }
    };
    
    int main() {
        Animal* animal;
    
        Dog dog;
        Cat cat;
    
        // Animal pointer pointing to Dog object
        animal = &dog;
        animal->sound();  // Output: Dog barks
    
        // Animal pointer pointing to Cat object
        animal = &cat;
        animal->sound();  // Output: Cat meows
    
        return 0;
    }
    

    Explanation:

    In this example:

    • The base class Animal has a virtual function sound(), which is overridden in the derived classes Dog and Cat.
    • In the main() function, we use a base class pointer (animal) to refer to objects of both derived classes (Dog and Cat).
    • The correct sound() function is called based on the type of object the pointer points to, and this decision is made at runtime, hence it is runtime polymorphism.

    Key Points:

    • The base class function is marked as virtual, enabling dynamic dispatch.
    • The sound() method in both Dog and Cat overrides the base class function. At runtime, the appropriate version of sound() is called based on the actual object type (Dog or Cat), not the pointer type.
    Dynamic Binding

    Dynamic binding (also known as late binding) is the process where the function call is resolved at runtime. When a base class pointer or reference points to a derived class object, the function call is bound to the correct method in the derived class.

    Virtual Destructor

    To ensure proper cleanup of resources when an object is destroyed, virtual destructors are used. If a base class has a virtual function and the object is deleted through a base class pointer, the derived class destructor is called, which ensures proper destruction of the object.

    Example of Virtual Destructor:
    #include <iostream>
    using namespace std;
    
    class Base {
    public:
        // Virtual destructor
        virtual ~Base() {
            cout << "Base Destructor" << endl;
        }
    };
    
    class Derived : public Base {
    public:
        ~Derived() override {
            cout << "Derived Destructor" << endl;
        }
    };
    
    int main() {
        Base* b = new Derived;
        delete b;  // Calls the derived class destructor, then the base class destructor
        return 0;
    }
    

    Explanation:

    In this example, a Derived object is created but deleted through a base class pointer. Since the destructor in the base class is virtual, the derived class destructor is called first, followed by the base class destructor, ensuring that the resources are properly freed.

    Advantages of Polymorphism

    1. Flexibility and Extensibility: Polymorphism allows you to design systems where new classes can be introduced without changing existing code. For instance, new types of Animal can be added without modifying the code that uses Animal pointers or references.

    2. Code Reusability: Common operations can be defined in base classes, and derived classes can override or extend these operations. This promotes reusability, reducing code duplication.

    3. Simplified Code: Polymorphism simplifies code, especially in cases where similar operations are performed on different object types. It allows for cleaner and more maintainable code by handling different types in a unified manner.

    Disadvantages of Polymorphism

    1. Performance Overhead: There is a performance cost for runtime polymorphism because of the overhead of dynamic dispatch. Each call to a virtual function involves a lookup in the virtual table (vtable), which can be slower than static function calls.

    2. Increased Complexity: Polymorphism, especially dynamic polymorphism, can introduce complexity into the code. It can be harder to track method calls and debug issues when multiple classes with similar interfaces are involved.

    3. Memory Overhead: Virtual functions introduce memory overhead due to the virtual table (vtable), which stores addresses of virtual functions. This increases memory usage, especially in large programs with many polymorphic classes.

    Summary

    • Polymorphism enables objects of different types to be treated as objects of a common base type, allowing for a unified interface.
    • Compile-time polymorphism is resolved at compile time, and includes function overloading and operator overloading.
    • Runtime polymorphism is resolved at runtime and is achieved using virtual functions and function overriding, allowing for dynamic method invocation.
    • Polymorphism promotes code reusability, extensibility, and flexibility, but can introduce performance overhead and complexity.
    Previous topic 15
    Multiple Inheritances
    Next topic 17
    Abstract Classes

    Past Papers

    Open this section to load past papers

    Click on Show Past Papers to see past papers.
    On This Page
      Reading Stats
      Est. reading time8 min
      Word count1,398
      Code examples0
      DifficultyIntermediate