Virtual Function and Polymorphism

By Notes Vandar

5.1 Concept of Pointer

Pointers are a fundamental feature in programming languages like C and C++. They provide a powerful way to manage memory and enable more complex data structures. Here’s a breakdown of what pointers are, how they work, and their significance in programming.

Definition of Pointer

A pointer is a variable that stores the memory address of another variable. Pointers allow for direct memory access and manipulation, enabling efficient memory management and dynamic data structure implementations.

Key Concepts of Pointers

  1. Memory Address:
    • Each variable in a program is stored at a specific location in memory, known as its address. A pointer holds this address rather than the actual data.
  2. Pointer Declaration:
    • Pointers are declared by specifying the data type they point to, followed by an asterisk (*). For example:
      int *p; // Declares a pointer to an integer
      char *c; // Declares a pointer to a character
  3. Pointer Initialization:
    • A pointer must be initialized with the address of a variable. This can be done using the address-of operator (&):
      int a = 10;
      int *p = &a; // p now holds the address of variable a
  4. Dereferencing a Pointer:
    • To access or modify the value at the memory address a pointer points to, you use the dereference operator (*):
      int value = *p; // Retrieves the value at the address stored in p
      *p = 20; // Changes the value of a to 20
  5. Null Pointer:
    • A pointer that is not assigned to any valid memory address is known as a null pointer. It can be explicitly set to nullptr in C++ (or NULL in C):
      int *p = nullptr; // p points to nothing
  6. Pointer Arithmetic:
    • Pointers can be incremented or decremented, which moves them to point to the next or previous memory location, respectively. This is particularly useful when dealing with arrays.
      int arr[3] = {1, 2, 3};
      int *p = arr; // p points to the first element
      p++; // Now p points to the second element (arr[1])

Example: Using Pointers

Here’s a simple example demonstrating the use of pointers in C++:

#include <iostream>
using namespace std;

int main() {
int a = 5;
int *p = &a; // Pointer p holds the address of a

cout << “Value of a: ” << a << endl; // Output: 5
cout << “Address of a: ” << &a << endl; // Output: Address of a
cout << “Value of p (address of a): ” << p << endl; // Address of a
cout << “Value pointed by p: ” << *p << endl; // Output: 5

*p = 10; // Changing the value of a using pointer
cout << “New value of a: ” << a << endl; // Output: 10

return 0;
}

Output:

Value of a: 5
Address of a: 0x7ffee6c56a2c // (example address)
Value of p (address of a): 0x7ffee6c56a2c // Same address
Value pointed by p: 5
New value of a: 10

Significance of Pointers

  1. Dynamic Memory Management:
    • Pointers are essential for dynamic memory allocation. They allow for the creation and management of data structures like linked lists, trees, and graphs.
  2. Efficient Array Handling:
    • Pointers can simplify array manipulation and iteration, enabling more flexible and efficient code.
  3. Function Arguments:
    • Pointers enable passing large data structures (like arrays or objects) to functions without copying them, improving performance.
  4. Low-Level Memory Access:
    • Pointers provide low-level access to memory, which is crucial for system programming, embedded systems, and performance-critical applications.

5.2 Need for Virtual Functions

Virtual functions are a crucial concept in object-oriented programming (OOP), particularly in C++. They provide a mechanism for achieving runtime polymorphism, allowing a program to decide which function to invoke at runtime rather than at compile time. Here’s a detailed explanation of why virtual functions are needed and how they benefit the design of object-oriented systems.

Key Reasons for Using Virtual Functions

  1. Runtime Polymorphism:
    • Virtual functions enable dynamic binding, which means that the method called is determined at runtime based on the type of the object pointed to, not the type of the pointer/reference. This allows for more flexible and reusable code.
  2. Base Class Pointer/Reference:
    • When a base class pointer or reference points to a derived class object, virtual functions ensure that the derived class’s overridden function is called. This is essential in scenarios where you want to treat different derived classes uniformly through a common base class interface.
  3. Extensibility:
    • By using virtual functions, you can add new derived classes with their own implementations of base class functions without modifying the existing code that uses the base class. This promotes the Open/Closed Principle of OOP, which states that software entities should be open for extension but closed for modification.
  4. Interface Definition:
    • Virtual functions provide a way to define a common interface for a group of related classes. Each derived class can implement its version of the function, ensuring that all classes adhere to the interface while allowing for specific behavior.
  5. Improved Code Maintainability:
    • Code that uses virtual functions can be easier to maintain and extend. New functionalities can be added with minimal changes to existing code, reducing the risk of introducing bugs.

Example: Virtual Functions in Action

Here’s a simple example to illustrate the need for virtual functions:

#include <iostream>
using namespace std;

// Base class
class Shape {
public:
virtual void draw() { // Virtual function
cout << “Drawing a shape.” << endl;
}
};

// Derived class 1
class Circle : public Shape {
public:
void draw() override { // Overriding the base class function
cout << “Drawing a circle.” << endl;
}
};

// Derived class 2
class Square : public Shape {
public:
void draw() override { // Overriding the base class function
cout << “Drawing a square.” << endl;
}
};

int main() {
Shape* shape1 = new Circle(); // Base class pointer pointing to derived class object
Shape* shape2 = new Square(); // Base class pointer pointing to another derived class object

shape1->draw(); // Calls Circle’s draw
shape2->draw(); // Calls Square’s draw

delete shape1; // Free memory
delete shape2; // Free memory
return 0;
}

Output:

Drawing a circle.
Drawing a square.

Explanation of the Example:

  1. Base Class and Virtual Function:
    • The Shape class defines a virtual function draw(), which can be overridden by derived classes.
  2. Derived Classes:
    • The Circle and Square classes both override the draw() function to provide their specific implementations.
  3. Base Class Pointer:
    • In the main function, Shape pointers shape1 and shape2 are created to point to Circle and Square objects, respectively.
  4. Dynamic Binding:
    • When draw() is called on these pointers, the correct overridden function is invoked based on the actual object type (Circle or Square), demonstrating runtime polymorphism.

Benefits of Using Virtual Functions

  • Flexibility: Virtual functions provide flexibility in how objects behave at runtime.
  • Code Reusability: You can reuse code that works with base class pointers/references without needing to know about derived class implementations.
  • Maintainability: Code is easier to maintain and extend, as new derived classes can be added without changing existing logic.

 

5.3 Definition of Virtual Function

A virtual function is a member function in a base class that is declared using the keyword virtual. It is designed to be overridden in derived classes. When you call a virtual function using a base class pointer or reference, the program determines at runtime which function implementation to execute based on the actual object type being pointed to. This mechanism is essential for achieving runtime polymorphism in object-oriented programming.

Key Characteristics of Virtual Functions:

  1. Declared in Base Class:
    • A virtual function is declared in a base class with the virtual keyword.
    • Syntax example:
      class Base {
      public:
      virtual void display() {
      cout << “Display from Base class” << endl;
      }
      };
  2. Overriding in Derived Class:
    • A derived class can override the virtual function to provide its specific implementation.
    • Syntax example:
      class Derived : public Base {
      public:
      void display() override {
      cout << “Display from Derived class” << endl;
      }
      };
  3. Dynamic Binding:
    • When a virtual function is called on a base class pointer or reference, the actual function that gets executed is determined at runtime, based on the type of the object being pointed to.
    • This is known as dynamic binding or late binding.
  4. Polymorphism:
    • Virtual functions enable polymorphism, allowing for the use of a single interface (base class) to represent different underlying forms (derived classes).
  5. Virtual Destructors:
    • If a class has virtual functions, it is good practice to declare its destructor as virtual. This ensures that the derived class’s destructor is called when an object is deleted through a base class pointer, preventing resource leaks.

Example: Virtual Function Implementation

Here’s a simple example illustrating the definition and use of virtual functions:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
virtual void sound() { // Virtual function
cout << “Some animal sound” << endl;
}
};

// Derived class
class Dog : public Animal {
public:
void sound() override { // Overriding the base class function
cout << “Bark” << endl;
}
};

// Derived class
class Cat : public Animal {
public:
void sound() override { // Overriding the base class function
cout << “Meow” << endl;
}
};

int main() {
Animal* animal1 = new Dog(); // Base class pointer pointing to Dog
Animal* animal2 = new Cat(); // Base class pointer pointing to Cat

animal1->sound(); // Calls Dog’s sound function
animal2->sound(); // Calls Cat’s sound function

delete animal1; // Free memory
delete animal2; // Free memory
return 0;
}

Output:

Bark
Meow

Explanation of the Example:

  1. Base Class with Virtual Function:
    • The Animal class contains a virtual function sound(), which can be overridden by any derived class.
  2. Derived Classes:
    • The Dog and Cat classes provide their implementations of the sound() function.
  3. Base Class Pointer:
    • In the main function, Animal pointers are used to point to Dog and Cat objects.
  4. Dynamic Binding:
    • When sound() is called on these pointers, the appropriate overridden function is invoked based on the actual object type, demonstrating the concept of virtual functions.

5.4 Pure Virtual Function

A pure virtual function is a virtual function in a base class that is declared with the syntax = 0 at the end of its declaration. This function does not have an implementation in the base class, and it must be overridden in any derived class that is to be instantiated. The presence of a pure virtual function makes the base class an abstract class, which cannot be instantiated on its own.

Key Characteristics of Pure Virtual Functions:

  1. Declaration:
    • A pure virtual function is declared in the base class using the syntax virtual void functionName() = 0;.
    • Example:
      class Base {
      public:
      virtual void display() = 0; // Pure virtual function
      };
  2. Abstract Class:
    • If a class contains at least one pure virtual function, it becomes an abstract class. This means you cannot create objects of this class directly.
    • Example:
      class Abstract {
      public:
      virtual void someFunction() = 0; // Abstract class
      };
  3. Enforcement of Overriding:
    • Derived classes must provide an implementation for the pure virtual function in order to be instantiated. If a derived class does not override the pure virtual function, it also becomes an abstract class.
  4. Polymorphism:
    • Pure virtual functions support polymorphism, allowing for dynamic method resolution in derived classes.

Example: Pure Virtual Function Implementation

Here’s a simple example illustrating the use of pure virtual functions:

#include <iostream>
using namespace std;

// Abstract class with a pure virtual function
class Shape {
public:
virtual void draw() = 0; // Pure virtual function
};

// Derived class implementing the pure virtual function
class Circle : public Shape {
public:
void draw() override { // Overriding the pure virtual function
cout << “Drawing a circle.” << endl;
}
};

// Derived class implementing the pure virtual function
class Square : public Shape {
public:
void draw() override { // Overriding the pure virtual function
cout << “Drawing a square.” << endl;
}
};

int main() {
Shape* shape1 = new Circle(); // Base class pointer pointing to Circle
Shape* shape2 = new Square(); // Base class pointer pointing to Square

shape1->draw(); // Calls Circle’s draw function
shape2->draw(); // Calls Square’s draw function

delete shape1; // Free memory
delete shape2; // Free memory
return 0;
}

Output:

Drawing a circle.
Drawing a square.

Explanation of the Example:

  1. Abstract Class:
    • The Shape class is declared as an abstract class because it contains a pure virtual function draw().
  2. Derived Classes:
    • The Circle and Square classes derive from Shape and provide their implementations of the draw() function.
  3. Base Class Pointer:
    • In the main function, pointers of type Shape are used to point to objects of Circle and Square.
  4. Dynamic Binding:
    • When draw() is called on these pointers, the appropriate derived class function is invoked based on the actual object type, demonstrating polymorphism through the use of pure virtual functions.

5.5 Abstract Class

An abstract class is a class that cannot be instantiated and is typically used as a base class for other classes. It serves as a blueprint for derived classes and may contain one or more pure virtual functions, which must be overridden in derived classes. Abstract classes are essential in object-oriented programming for defining interfaces and establishing a common structure for derived classes.

Key Characteristics of Abstract Classes:

  1. Contains Pure Virtual Functions:
    • An abstract class is defined by having at least one pure virtual function (a function declared as virtual void functionName() = 0;).
    • Example:
      class AbstractShape {
      public:
      virtual void draw() = 0; // Pure virtual function
      };
  2. Cannot Be Instantiated:
    • You cannot create objects of an abstract class directly. This ensures that only derived classes that implement the pure virtual functions can be instantiated.
    • Example:
      AbstractShape shape; // Error: cannot instantiate abstract class
  3. Provides Interface:
    • Abstract classes provide a common interface for all derived classes, enforcing a certain structure while allowing flexibility in implementation.
    • This helps in designing systems that are modular and adhere to the Open/Closed Principle of object-oriented design.
  4. Can Have Concrete Functions:
    • An abstract class can also contain concrete (non-pure) member functions with implementations. This allows shared functionality among derived classes while still enforcing the overriding of specific methods.
    • Example:
      class AbstractShape {
      public:
      virtual void draw() = 0; // Pure virtual function
      void show() {
      cout << “Showing shape.” << endl;
      }
      };

Example: Abstract Class Implementation

Here’s a simple example illustrating the use of an abstract class:

#include <iostream>
using namespace std;

// Abstract class
class Animal {
public:
virtual void sound() = 0; // Pure virtual function
};

// Derived class implementing the pure virtual function
class Dog : public Animal {
public:
void sound() override { // Overriding the pure virtual function
cout << “Bark” << endl;
}
};

// Derived class implementing the pure virtual function
class Cat : public Animal {
public:
void sound() override { // Overriding the pure virtual function
cout << “Meow” << endl;
}
};

int main() {
Animal* animal1 = new Dog(); // Base class pointer pointing to Dog
Animal* animal2 = new Cat(); // Base class pointer pointing to Cat

animal1->sound(); // Calls Dog’s sound function
animal2->sound(); // Calls Cat’s sound function

delete animal1; // Free memory
delete animal2; // Free memory
return 0;
}

Output:

Bark
Meow

Explanation of the Example:

  1. Abstract Class:
    • The Animal class is defined as an abstract class because it contains a pure virtual function sound().
  2. Derived Classes:
    • The Dog and Cat classes inherit from Animal and provide implementations for the sound() function.
  3. Base Class Pointer:
    • In the main function, Animal pointers are used to point to objects of Dog and Cat.
  4. Dynamic Binding:
    • When sound() is called on these pointers, the corresponding derived class method is executed, demonstrating polymorphism.

Advantages of Using Abstract Classes:

  • Code Reusability: Abstract classes allow shared code (e.g., common functions) while enforcing derived classes to implement specific behaviors.
  • Interface Definition: They define a clear contract (interface) for derived classes, ensuring consistency.
  • Flexible Design: Abstract classes enable the design of systems that are easy to extend and modify, adhering to object-oriented design principles.

5.6 Container Class

A container class is a class designed to store and manage collections of objects, often providing various methods for manipulating the data it holds. Container classes are a fundamental part of many programming languages and frameworks, particularly in object-oriented programming, as they encapsulate data structures and provide an interface to interact with collections of objects efficiently.

Key Characteristics of Container Classes:

  1. Storage of Objects:
    • Container classes can hold multiple objects, which can be of the same type or different types, depending on the container’s design.
    • Common types of containers include arrays, lists, sets, maps, queues, and stacks.
  2. Encapsulation:
    • Container classes encapsulate the underlying data structures and provide methods to manipulate the data without exposing the internal implementation details.
    • This helps in maintaining data integrity and abstraction.
  3. Dynamic Size:
    • Many container classes allow dynamic resizing, meaning they can grow or shrink as needed. This is especially true for classes such as vectors and lists in languages like C++ or Java.
  4. Iterators:
    • Container classes often provide iterator functionality, allowing users to traverse the elements in a collection without needing to know the underlying structure.
    • Iterators simplify the process of accessing elements, making the code cleaner and more maintainable.
  5. Template Classes:
    • In languages like C++, container classes can be defined as template classes, allowing them to hold any data type. This promotes code reuse and type safety.

Common Examples of Container Classes

  1. Standard Template Library (STL) in C++:
    • The STL provides several container classes, including:
      • Vector: A dynamic array that can grow in size.
      • List: A doubly linked list that allows efficient insertion and deletion.
      • Map: An associative array that stores key-value pairs.
      • Set: A collection of unique elements.
  2. Java Collections Framework:
    • Java provides a rich set of container classes, such as:
      • ArrayList: A resizable array implementation.
      • LinkedList: A linked list implementation.
      • HashMap: A hash table implementation for key-value pairs.
      • HashSet: A set implementation based on a hash table.

Example: Container Class in C++

Here’s an example illustrating a simple container class that stores integers:

#include <iostream>
#include <vector>

class IntContainer {
private:
std::vector<int> data; // Vector to hold integers

public:
// Method to add an integer
void add(int value) {
data.push_back(value); // Add value to the container
}

// Method to display contents
void display() const {
std::cout << “Contents of IntContainer: “;
for (int value : data) {
std::cout << value << ” “;
}
std::cout << std::endl;
}

// Method to get the size of the container
size_t size() const {
return data.size();
}
};

int main() {
IntContainer container; // Create an instance of IntContainer

container.add(10); // Add elements to the container
container.add(20);
container.add(30);

container.display(); // Display the contents of the container
std::cout << “Size of container: ” << container.size() << std::endl; // Display the size

return 0;
}

Output:

Contents of IntContainer: 10 20 30
Size of container: 3

Explanation of the Example:

  1. Container Class:
    • The IntContainer class is a simple container class that uses a std::vector to hold integers.
  2. Methods:
    • The add() method allows adding integers to the container.
    • The display() method prints the contents of the container.
    • The size() method returns the number of elements in the container.
  3. Usage:
    • In the main() function, an instance of IntContainer is created, and integers are added and displayed.

 

Important Questions
Comments
Discussion
0 Comments
  Loading . . .