Unit 5
Unit 5
UNIT 5
POLYMORPHISM, OVERLOADING, AND SMART POINTERS
Output:
Operator Overloading
• Operator overloading allows customizing the behavior of
operators for user-defined types (like classes). This helps in
making objects interact naturally using operators like +, -, *, etc.,
similar to primitive types.
• Types of Operators:
• Unary Operators (+, -, ++, --)
• Binary Operators (+, -, *, /, %, ==, !=) Syntax:
• Special Operators ([], (), ->, =)
return_type operator<symbol>(arguments)
{
// Custom implementation
}
Overloading Binary Operators
Output:
Run-time Polymorphism
• Run-time polymorphism is achieved using function overriding
and virtual functions in C++.
• It allows a derived class to provide a specific implementation of a
function that is already defined in its base class.
• The function call is resolved at runtime using dynamic binding
(late binding).
Function Overriding (Virtual Functions)
• Function overriding allows a derived class to provide a specific
implementation of a function that is already defined in its base
class.
• The base class function is marked as virtual to enable dynamic.
• Virtual functions enable dynamic dispatch, meaning the function
that gets executed is determined at runtime.
Function Overriding Example
// C++ program to demonstrate compile time {
function overriding cout << "Derived Function" << endl;
}
#include <iostream> };
using namespace std;
int main()
class Parent { {
public: Child Child_Derived;
void GeeksforGeeks_Print() Child_Derived.GeeksforGeeks_Print();
{ return 0;
cout << "Base Function" << endl; }
}
};
Output:
class Child : public Parent {
public:
void GeeksforGeeks_Print()
Function Overriding (Virtual Functions) Example
#include <iostream>
using namespace std; int main() {
Animal* a; // Pointer to base class
class Animal { Dog d;
public: Cat c;
virtual void makeSound() { // Virtual function
cout << "Animal makes a sound." << endl; a = &d;
} a->makeSound(); // Calls Dog's makeSound() due to dynamic binding
};
a = &c;
class Dog : public Animal { a->makeSound(); // Calls Cat's makeSound() due to dynamic binding
public:
void makeSound() override { // Overriding function return 0;
cout << "Dog barks." << endl; }
}
};
Syntax:
class Base {
public:
virtual void functionName() = 0; // Pure virtual function
};
Pure Virtual Functions & Abstract Classes Example
Output:
C++ Type Conversion
• Type conversion in C++ refers to converting one data type into
another. It can be categorized into implicit and explicit type
conversion.
Implicit Type Conversion (Type
Promotion)
• Also known as automatic type conversion, this happens when
the compiler automatically converts one data type into another
without explicit intervention by the programmer.
int main() {
int num = 10;
double dbl = num; // Implicit conversion from int to double Output:
return 0;
}
Explicit Type Conversion (Type Casting)
• Explicit conversion requires the programmer to specify the type conversion
manually using type casting. There are three ways to perform explicit type
conversion in C++:
• C-Style Cast
• C++ Static Cast (static_cast)
• Other C++ Casts
C++ provides other type casting methods for specific use cases:
•dynamic_cast (for polymorphic class conversions)
•const_cast (to add/remove const from variables)
•reinterpret_cast (for low-level pointer manipulation)
C-Style Cast ((type)variable)
#include <iostream>
using namespace std;
int main() {
double num = 9.99; Output:
int x = (int)num; // Explicit conversion using C-style cast
cout << "Original value (double): " << num << endl;
cout << "Converted value (int): " << x << endl; // Output: 9
return 0;
}
static_cast<> (Recommended in C++)
#include <iostream>
using namespace std;
int main() {
double num = 9.99;
int x = static_cast<int>(num); // Explicit conversion using static_cast
cout << "Original value (double): " << num << endl;
cout << "Converted value (int): " << x << endl; // Output: 9
return 0;
}
Output:
reinterpret_cast<> (For Pointer Conversion)
#include <iostream>
using namespace std;
int main() {
int num = 65;
char* ptr = reinterpret_cast<char*>(&num); // Reinterpreting int memory as char*
cout << "Reinterpreted character: " << *ptr << endl; // Output: 'A' (ASCII 65)
return 0;
}
Output:
const_cast<> (Removing const Qualifier)
#include <iostream>
using namespace std;
int main() {
const int num = 10; Output:
int* ptr = const_cast<int*>(&num); // Removing const
modify(ptr);
cout << "Modified value: " << *ptr << endl; // Output: 42 (Undefined behavior warning!)
return 0;
}
dynamic_cast<> (For Polymorphic Classes)
#include <iostream>
using namespace std; if (derivedPtr) {
cout << "Successfully casted to Derived!" << endl;
class Base { } else {
public: cout << "Failed to cast!" << endl;
virtual void show() {} // Virtual function makes Base }
polymorphic
}; delete basePtr;
return 0;
class Derived : public Base {}; }
int main() {
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
// Safe downcasting
Output:
User-Defined Type Conversion
• C++ allows defining custom type conversions in classes by using:
• Conversion constructors
• Conversion operators
Example: Conversion Constructor
#include<iostream>
using namespace std;
class MyClass {
public:
int value;
MyClass(int v) { value = v; } // Conversion constructor
};
Output:
int main() {
MyClass obj = 100; // Implicit conversion from int to MyClass
cout << obj.value; // Output: 100
}
Example: Conversion Operator
#include<iostream>
using namespace std;
class MyClass {
public:
int value;
MyClass(int v) : value(v) {}
int main() {
MyClass obj(200);
int x = obj; // Implicit conversion from MyClass to int
cout << x; // Output: 200
}
Relationships among Objects in an
Inheritance Hierarchy
• Inheritance in C++ establishes relationships among objects based on a
parent-child hierarchy. It allows a class (derived class) to inherit properties
and behaviors from another class (base class). This promotes code reuse,
abstraction, and polymorphism.
IS-A Relationship (Generalization &
Specialization)
Example:
• A Car is a Vehicle.
• A Dog is an Animal.
Example
#include <iostream>
using namespace std;
int main() {
Car myCar;
myCar.show(); // Inherited method
myCar.display(); // Car-specific method
return 0;
}
HAS-A Relationship
(Composition/Aggregation)
• Represents "has a" relationship.
• One class contains an instance (or pointer) of another class.
Example:
• A Car has a Engine.
• A Company has a Employee.
Example
#include <iostream>
using namespace std; int main() {
Car myCar;
class Engine { myCar.startCar();
public: return 0;
void start() { cout << "Engine Started" << endl; } }
};
class Car {
private:
Engine engine; // Car has an Engine
public:
void startCar() {
engine.start(); // Using Engine's functionality
cout << "Car is Moving" << endl;
}
};
Output:
Other Relationships
• POLYMORPHISM (Dynamic Behavior)
• A derived class can override the behavior of a base class.
• Achieved via function overriding and virtual functions.
Output:
Virtual Destructors
• A virtual destructor ensures that the destructor of a derived
class is called properly when deleting an object through a base
class pointer.
class Base {
public:
virtual ~Base() { cout << "Base Destructor" << endl; }
};
int main() {
Base* ptr = new Derived();
delete ptr; // Calls both Base and Derived destructors
return 0;
}
Abstract Classes and Pure Virtual Functions
• In C++, abstract classes and pure virtual functions play a crucial
role in defining interfaces and enabling polymorphism.
• They allow us to create base classes that define a common
structure for derived classes while enforcing the implementation
of specific functions.
Pure Virtual Functions
• A pure virtual function is a function that must be overridden in a
derived class. It is declared in a base class with = 0.
Output:
Abstract Classes
• An abstract class is a class that cannot be instantiated on its
own and is meant to be a base class for other classes.
• It contains at least one pure virtual function.
Output:
Example Abstract Class with Concrete
Functions
#include <iostream> };
using namespace std;
int main() {
class Animal { Cat c;
public: c.breathe(); // Output: Breathing... (from base class)
virtual void makeSound() = 0; // Pure virtual function c.makeSound(); // Output: Meow! (overridden)
void breathe() { // Concrete function return 0;
cout << "Breathing..." << endl; }
}
};
Output:
Dynamic Memory Management with new and delete
Syntax:
DataType* pointer = new DataType; // Allocates memory for a single variable
DataType* arrayPointer = new DataType[size]; // Allocates memory for an array
Deallocating Memory with delete
• The delete operator frees dynamically allocated memory to
prevent memory leaks.
Syntax:
int main() {
int* ptr = new int; // Allocates memory for an integer Output:
*ptr = 42; // Assign value to allocated memory
class Car {
public:
void start() {
cout << "Car is starting..." << endl;
}
};
Output:
int main() {
Car* myCar = new Car; // Allocates memory for a Car object
myCar->start(); // Call member function
class Car {
public:
Car() { cout << "Car Created" << endl; }
~Car() { cout << "Car Destroyed" << endl; } Output:
};
int main() {
Car* cars = new Car[3]; // Allocates an array of 3 Car objects
• It ensures that resources such as memory, file handles, sockets, and locks
are properly released when an object goes out of scope.
~RAII() {
delete data; // Resource release (freeing memory)
cout << "Resource deallocated" << endl; Output:
}
};
How RAII Works in previous Example
• Constructor allocates memory (new int(value)).
• Destructor releases memory (delete data).
• When obj goes out of scope, the destructor is called automatically.
• No manual delete is needed, preventing memory leaks.
Smart Pointers
• C++ smart pointers (unique_ptr, shared_ptr, weak_ptr) in
<memory> automate RAII for dynamically allocated objects.
Smart Pointers
• unique_ptr: Exclusive Ownership
• A unique_ptr ensures only one pointer manages an object. When it goes out of scope, it automatically deletes the
resource.
int main() {
unique_ptr<Car> car1 = make_unique<Car>(); // Creates Car object
car1->drive();
Output:
Example of shared_ptr
#include <iostream>
#include <memory> // Required for smart pointers car2->drive();
using namespace std;
return 0; // Car is destroyed when last shared_ptr goes out of scope
class Car { }
public:
Car() { cout << "Car Created\n"; }
~Car() { cout << "Car Destroyed\n"; }
void drive() { cout << "Driving...\n"; }
};
int main() {
shared_ptr<Car> car1 = make_shared<Car>(); // Car is created
shared_ptr<Car> car2 = car1; // Shared ownership
cout << "Reference count: " << car1.use_count() << endl; // Output: 2
Output:
Example of weak_ptr
#include <iostream> int main() {
#include <memory> shared_ptr<A> a = make_shared<A>();
using namespace std; shared_ptr<B> b = make_shared<B>();
class B; // Forward declaration a->b_ptr = b; // Weak reference (does not increase reference count)
b->a_ptr = a; // Shared reference (increases reference count)
class A {
public: return 0; // Both objects are destroyed correctly
weak_ptr<B> b_ptr; }
// Weak reference to avoid circular dependency
~A() { cout << "A Destroyed\n"; }
};
class B {
public:
shared_ptr<A> a_ptr;
~B() { cout << "B Destroyed\n"; }
};
Output:
Three-Way Comparison Operator(< = >)
• The three-way comparison operator (<=>), also called the
spaceship operator, was introduced in C++20. It simplifies
comparisons by automatically generating ==, <, >, <=, and >=
operators.
int main() {
int a = 5, b = 10;
if (result < 0)
cout << "a is less than b\n";
else if (result > 0)
cout << "a is greater than b\n";
else
cout << "a is equal to b\n";
return 0;
}
Explicit Constructors and Conversion
Operators
• C++ allows implicit and explicit conversions between types.
• However, sometimes implicit conversions can cause unexpected
behaviors.
• To control this, explicit constructors and explicit conversion
operators are used.
Explicit Constructor
• By default, a single-argument constructor can be used for implicit
conversion, but marking it as explicit prevents unintended
conversions.
void printNumber(Number n) {
cout << "Number: " << n.value << endl;
}
Output:
Explicit Constructor (Prevents Implicit
Conversion)
#include <iostream>
using namespace std; int main() {
// printNumber(10); // ❌ ERROR: Implicit conversion is blocked
class Number {
public: printNumber(Number(10)); // ✅ OK: Explicit conversion using
int value; constructor
return 0;
// Constructor WITH explicit (Prevents implicit conversion) }
explicit Number(int v) : value(v) {
cout << "Constructor called\n";
}
};
void printNumber(Number n) {
cout << "Number: " << n.value << endl;
}
Output:
Conversion Operators
• Conversion operators allow converting a class object into
another type.
• Implicit conversion operators allow automatic conversion.
• Explicit conversion operators require manual conversion.
Implicit Conversion Operator
#include <iostream> Number num(42);
using namespace std; int x = num; // Implicitly converts Number → int
class Number { cout << "Converted int: " << x << endl; // Output: 42
public: return 0;
int value; }
Number(int v) : value(v) {}
int main() {
Output:
Explicit Conversion Operator
#include <iostream> int main() {
using namespace std; Number num(42);
Output:
Overloading the Function Call Operator ()
• The function call operator () can be overloaded in C++ to allow an
object to be used like a function.
• This is useful for functors (function objects), which are commonly
used in STL algorithms, callbacks, and functional programming.
class ClassName {
public:
ReturnType operator()(ParameterList) {
Syntax: // Function body
}
};
Example
#include <iostream> int main() {
Adder add5(5); // Create an object with value 5
class Adder {
private: std::cout << "add5(10): " << add5(10) << std::endl; // Calls
int value; operator(), output: 15
public:
Adder(int val) : value(val) {} return 0;
}
// Overloading operator()
int operator()(int num) {
return value + num;
}
};
Output:
Example
#include <iostream>
class Multiplier {
public:
int operator()(int a, int b) {
return a * b;
}
};
int main() {
Multiplier multiply;
std::cout << "multiply(4, 5): " << multiply(4, 5) << std::endl; // Output: 20
Output:
return 0;
}
Example
#include <iostream> Counter counter;
int main() {