Unit 2

Methods & Polymorphism

Constructors, Destructors, Overloading, Overriding, Virtual Functions & UML Interaction Diagrams

← Home
01
Constructors
02
Destructor
03
Constructor Overloading
04
Method Overloading
05
Operator Overloading
06
Function Overriding
07
Sequence Diagram
08
Collaboration Diagram
C++Compiler
01

Constructors in C++

Real-Life AnalogyWhen a new employee joins a company, HR automatically fills their profile on day 1. A constructor does the same — it automatically initializes an object the moment it is created.

Definition

A constructor is a special member function that is automatically called when an object is created. It initializes the object's data members.

Characteristics of Constructor

✓ Same name as the class   ✓ No return type (not even void)   ✓ Automatically called on object creation   ✓ Can be overloaded

Types of Constructors

Type Arguments Purpose
Default Constructor None Sets default values
Parameterized Constructor One or more Initializes with specific values
Copy Constructor Reference of same class Creates copy of existing object

1. Default Constructor

A constructor with no arguments. Called automatically when object is created without passing values.

#include<iostream>
using namespace std;
class Student{
    int roll; string name;
public:
    Student(){          // Default Constructor
        roll=1; name="Abinaya";
        cout<<"Default Constructor Called"<<endl;
    }
    void display(){
        cout<<"Roll No: "<<roll<<endl;
        cout<<"Name: "<<name<<endl;
    }
};
int main(){
    Student s;    // Default constructor auto-called
    s.display();
    return 0;
}
► Expected Output
Default Constructor Called Roll No: 1 Name: Abinaya

2. Parameterized Constructor

A constructor that accepts arguments to initialize an object with specific values when created.

#include<iostream>
using namespace std;
class Student{
private:
    int rollNo; string name; float marks;
public:
    Student(int r, string n, float m){   // Parameterized Constructor
        rollNo=r; name=n; marks=m;
    }
    void display(){
        cout<<"Roll No : "<<rollNo<<endl;
        cout<<"Name    : "<<name<<endl;
        cout<<"Marks   : "<<marks<<endl;
    }
};
int main(){
    Student s1(101,"Arun",88.5);   // values passed at creation
    s1.display();
    return 0;
}
► Expected Output
Roll No : 101 Name : Arun Marks : 88.5

3. Copy Constructor

Creates a new object by copying data from an existing object of the same class. Takes a const ClassName &obj parameter.

#include<iostream>
using namespace std;
class Student{
    int rollNo;
public:
    Student(int r){ rollNo=r; }                        // Parameterized
    Student(const Student &s){                         // Copy Constructor
        rollNo=s.rollNo;
        cout<<"Copy Constructor Called"<<endl;
    }
    void display(){ cout<<"Roll No: "<<rollNo<<endl; }
};
int main(){
    Student s1(101);       // parameterized constructor
    Student s2=s1;         // copy constructor called
    s1.display();
    s2.display();
    return 0;
}
► Expected Output
Copy Constructor Called Roll No: 101 Roll No: 101
1
Student s2 = s1; triggers the copy constructor — s2 gets a copy of s1's data.
2
const Student &s — const prevents the source object from being modified; & passes by reference to avoid infinite recursion.

Why const & Reference in Copy Constructor?

Deep Explanation

const — prevents the source object from being accidentally modified. It means: "I am only reading, not changing."

& (reference) — if we passed by value, C++ would copy the object to pass it — which would call the copy constructor again — causing infinite recursion. Reference avoids this.

Signature must always be: ClassName(const ClassName &obj)

When is Copy Constructor Called Automatically?

1. Student s2 = s1;  → initialization from another object
2. void show(Student s)  → object passed by value to a function
3. Student get(){ return s; }  → object returned by value from function

Copy Constructor vs Assignment Operator

Point Copy Constructor Assignment Operator
When Object is being created from another Object already exists, copying data
Example Student s2 = s1; (at creation) s2 = s1; (after s2 already exists)
Calls Copy constructor operator=
Common TrapStudent s2 = s1; calls COPY CONSTRUCTOR (not assignment). Assignment = is only used when the object already existed before the statement.
Exam Answers

2-mark: A constructor is a special member function with the same name as the class, no return type, and is automatically called when an object is created.

5-mark: Types — Default (no args, sets defaults), Parameterized (takes args, initializes with specific values), Copy (takes const reference, creates duplicate object). All three have same name as class and no return type.

#include <iostream>
using namespace std;
class BankAccount {
    string owner; double balance;
public:
    BankAccount(){ owner="Unknown"; balance=0; cout<<"Default constructor called"<<endl; }
    BankAccount(string o, double b){ owner=o; balance=b; cout<<"Parameterized: "<<owner<<endl; }
    BankAccount(const BankAccount &a){ owner=a.owner; balance=a.balance; cout<<"Copy constructor called"<<endl; }
    void display(){ cout<<owner<<" | Balance: "<<balance<<endl; }
};
int main(){
    BankAccount a1;
    BankAccount a2("Alice", 5000);
    BankAccount a3 = a2;
    a1.display(); a2.display(); a3.display();
    return 0;
}
► Expected Output
Default constructor called Parameterized: Alice Copy constructor called Unknown | Balance: 0 Alice | Balance: 5000 Alice | Balance: 5000
1
BankAccount a1 — no args → default constructor called. Sets owner='Unknown', balance=0.
2
BankAccount a2('Alice',5000) — two args → parameterized constructor. Stores Alice and 5000.
3
BankAccount a3=a2 — initializing from existing object → copy constructor. a3 gets a copy of a2's data.
4
const BankAccount &a in copy constructor: const prevents modifying source; & avoids infinite recursion.
5
All three constructors have same name (BankAccount), no return type, called automatically on object creation.
#include <iostream>
using namespace std;
class BankAccount { public: string owner; BankAccount(string n){ owner=n; cout<<"Account created for "<<owner; } };
int main(){ BankAccount acc("Alice"); return 0; }
► Expected Output
Account created for Alice
1
Constructor has same name as class.
2
Automatically called on object creation.
3
Used to initialize data members.
🔨 Build From Scratch — Constructors
class T { T(){ cout<<"Hi"; } };
► Expected Output
Hi
1
Auto-initialization.
Memory LockConstructor = same name + no return type + auto-called
Default=no args | Parameterized=with args | Copy=const Ref&
s2=s1 → triggers copy constructor

02

Destructor

Real-Life AnalogyWhen an employee retires, HR automatically closes their account and releases all resources. A destructor does the same — it cleans up when an object is destroyed.

Definition

A destructor is a special member function that is automatically called when an object goes out of scope or is destroyed. It frees memory and resources used by the object.

Rules of Destructor

✓ Same name as class preceded by tilde (~)   ✓ No return type   ✓ No parameters   ✓ Only ONE destructor per class   ✓ Cannot be overloaded

Syntax

// Inside class
~ClassName() { // cleanup code }

// Outside class
ClassName::~ClassName() { // cleanup code }

Complete Program

#include<iostream>
using namespace std;
class Student{
public:
    Student(){ cout<<"Constructor called"<<endl; }
    ~Student(){ cout<<"Destructor called"<<endl; }
};
int main(){
    Student s;
    cout<<"Inside main function"<<endl;
    return 0;    // destructor auto-called here
}
► Expected Output
Constructor called Inside main function Destructor called
1
Constructor called when Student s; is created.
2
"Inside main function" prints next.
3
When main() ends, s goes out of scope — destructor auto-called.
Exam Answer — 5 marks

A destructor is a special member function with the same name as the class preceded by a tilde (~). It has no return type, no parameters, and cannot be overloaded. It is automatically invoked when the object goes out of scope. Its main purpose is to free memory/resources allocated by the constructor. Only one destructor per class is allowed. Syntax: ~ClassName() { }

Common MistakeTrying to overload the destructor: only ONE destructor per class is allowed. Also, never call destructor explicitly — it is automatically called.
#include <iostream>
using namespace std;
class FileHandler {
    string filename;
public:
    FileHandler(string f){ filename=f; cout<<"Opened: "<<filename<<endl; }
    ~FileHandler(){ cout<<"Closed: "<<filename<<endl; }
};
int main(){
    cout<<"--- entering block ---"<<endl;
    {
        FileHandler f1("data.txt");
        FileHandler f2("log.txt");
        cout<<"--- files in use ---"<<endl;
    }   // f2 destroyed first (LIFO), then f1
    cout<<"--- block exited ---"<<endl;
    return 0;
}
► Expected Output
--- entering block --- Opened: data.txt Opened: log.txt --- files in use --- Closed: log.txt Closed: data.txt --- block exited ---
1
Constructor called when object is created: f1 opens data.txt, then f2 opens log.txt.
2
When the block { } ends, objects go out of scope. Destructors called automatically.
3
LIFO order: f2 was created last → destroyed first ('Closed: log.txt'). Then f1 ('Closed: data.txt').
4
Destructor ~FileHandler(): same name with tilde, no return type, no parameters. Cannot be overloaded.
5
This pattern (open in constructor, close in destructor) is called RAII — ensures resources are always released.
#include <iostream>
using namespace std;
class Test { public: Test(){ cout<<"Born"; } ~Test(){ cout<<"Gone"; } };
int main(){ { Test t; } return 0; }
► Expected Output
BornGone
1
Destructor starts with ~ tilde.
2
Called when object goes out of scope.
3
No parameters or return type.
🔨 Build From Scratch — Destructors
class T { ~T(){ cout<<"Bye"; } };
► Expected Output
Bye
1
Auto-cleanup.
Memory LockDestructor = ~ + class name + no args + no return
Auto-called when object dies | Cannot overload | Frees resources

Destructor Call Order — Reverse of Constructor

Destructors are called in reverse order of construction (LIFO — Last In, First Out). The last object created is the first to be destroyed.

#include<iostream>
using namespace std;
class Box{
public:
    int id;
    Box(int n){ id=n; cout<<"Constructor: Box "<<id<<endl; }
    ~Box(){ cout<<"Destructor:  Box "<<id<<endl; }
};
int main(){
    Box b1(1);
    Box b2(2);
    Box b3(3);
    cout<<"--- end of main ---"<<endl;
    return 0;   // destructors called here in REVERSE
}
► Expected Output
Constructor: Box 1 Constructor: Box 2 Constructor: Box 3 --- end of main --- Destructor: Box 3 Destructor: Box 2 Destructor: Box 1
Exam TipThis is a very common output prediction question. Remember: Constructor = 1,2,3 (top to bottom). Destructor = 3,2,1 (bottom to top / LIFO).

03

Constructor Overloading

Real-Life AnalogyA coffee machine can make coffee with no sugar, with sugar, or with sugar and milk — same machine, different configurations. Constructor overloading works the same way: same class name, different sets of parameters.

Definition

Constructor overloading means having more than one constructor in a class with the same name but different parameter lists. The compiler calls the correct constructor based on the arguments passed.

Key Rules

✓ All constructors must have the same name (class name)
✓ Each must differ in number or type of arguments
✓ Compiler selects based on arguments at object creation

Complete Program

#include<iostream>
using namespace std;
class Student{
public:
    int roll; string name; float marks;

    Student(){                              // Default
        roll=0; name="Unknown"; marks=0;
        cout<<"Default Constructor"<<endl;
    }
    Student(int r, string n){               // 2-param
        roll=r; name=n; marks=0;
        cout<<"2-Param Constructor"<<endl;
    }
    Student(int r, string n, float m){     // 3-param
        roll=r; name=n; marks=m;
        cout<<"3-Param Constructor"<<endl;
    }
    void display(){
        cout<<"Roll:"<<roll<<" Name:"<<name<<" Marks:"<<marks<<endl;
    }
};
int main(){
    Student s1;                       // calls default
    Student s2(101,"Arun");            // calls 2-param
    Student s3(102,"Priya",91.5);     // calls 3-param
    s1.display(); s2.display(); s3.display();
    return 0;
}
► Expected Output
Default Constructor 2-Param Constructor 3-Param Constructor Roll:0 Name:Unknown Marks:0 Roll:101 Name:Arun Marks:0 Roll:102 Name:Priya Marks:91.5
Exam Answer

Constructor overloading is having multiple constructors in the same class with the same name but different parameters. The correct constructor is selected based on arguments passed during object creation. It is a form of compile-time polymorphism.

class Box { public: int s; Box(){ s=0; } Box(int x){ s=x; } };
int main(){ Box b1; Box b2(5); return 0; }
► Expected Output
1
Multiple constructors with different parameters.
2
Compiler chooses based on arguments.
3
Enables multiple ways to initialize objects.
Memory LockOverloading = same name + different parameters
Compiler picks constructor based on arguments at object creation

Constructor Call Order — Multiple Objects

When multiple objects are created, constructors are called in the order the objects are defined.

#include<iostream>
using namespace std;
class Box{
public:
    int id;
    Box(int n){ id=n; cout<<"Constructor: Box "<<id<<endl; }
};
int main(){
    Box b1(1);   // constructed 1st
    Box b2(2);   // constructed 2nd
    Box b3(3);   // constructed 3rd
    cout<<"All objects created"<<endl;
    return 0;
}
► Expected Output
Constructor: Box 1 Constructor: Box 2 Constructor: Box 3 All objects created
Exam TipConstructor order = order of definition (top to bottom). Destructor order = REVERSE (LIFO). See Destructor section below for proof.

04

Method (Function) Overloading

Real-Life AnalogyA calculator's + button adds integers, decimals, or even time values. Same button (+), different behavior based on input type. That is function overloading.

Definition

Function overloading allows two or more functions to have the same name but different parameters (type, number, or both). It is a form of compile-time polymorphism.

Key Rules

✓ Same function name
✓ Must differ in parameter type or count
✓ Return type alone is NOT enough to overload

Complete Program

#include<iostream>
using namespace std;

void add(int a, int b){
    cout<<"Int sum = "<<(a+b)<<endl;
}
void add(double a, double b){
    cout<<"Double sum = "<<(a+b)<<endl;
}
void add(int a, int b, int c){
    cout<<"Three sum = "<<(a+b+c)<<endl;
}
int main(){
    add(10,2);
    add(5.3,6.2);
    add(1,2,3);
    return 0;
}
► Expected Output
Int sum = 12 Double sum = 11.5 Three sum = 6

Quick Check (click to reveal)

Can two functions be overloaded only by changing return type?
No. Return type alone cannot distinguish overloaded functions. Parameters must differ in type or count.
Which type of polymorphism is function overloading?
Compile-time polymorphism (also called static polymorphism). The compiler resolves which function to call at compile time.
Exam Answer

Function overloading is defining multiple functions with the same name but different parameter lists. The compiler selects the correct function at compile time based on the arguments. It is an example of compile-time polymorphism. Key point: return type alone cannot be used to overload — parameters must differ.

#include <iostream>
using namespace std;
class Calculator {
public:
    int add(int a, int b){ return a+b; }
    double add(double a, double b){ return a+b; }
    int add(int a, int b, int c){ return a+b+c; }
    string add(string a, string b){ return a+b; }
};
int main(){
    Calculator c;
    cout<<"Int add:    "<<c.add(3,4)<<endl;
    cout<<"Double add: "<<c.add(1.5,2.5)<<endl;
    cout<<"Triple add: "<<c.add(1,2,3)<<endl;
    cout<<"String add: "<<c.add("Hello","World")<<endl;
    return 0;
}
► Expected Output
Int add: 7 Double add: 4 Triple add: 6 String add: HelloWorld
1
Four functions all named add. Each has different parameter types or counts.
2
c.add(3,4) — two ints → compiler selects int add(int,int).
3
c.add(1.5,2.5) — two doubles → compiler selects double add(double,double).
4
c.add(1,2,3) — three ints → selects three-param version.
5
c.add('Hello','World') — two strings → string concatenation version. Return type alone cannot distinguish overloads.
class Calc { public: int add(int a, int b){ return a+b; } double add(double a, double b){ return a+b; } };
► Expected Output
1
Same method name, different parameter types/counts.
2
Compile-time polymorphism.
3
Return type alone cannot differentiate.
Memory LockOverloading = same name + different params = compile-time polymorphism
Return type alone CANNOT distinguish overloaded functions

05

Operator Overloading

Real-Life AnalogyThe + sign means different things: 2+3=5 (numbers) or "Hello"+"World"="HelloWorld" (strings). Operator overloading lets you define what + means for your own class.

Definition

Operator overloading is a compile-time polymorphism that allows existing C++ operators to be given new meaning for user-defined data types (classes).

Syntax

return_type operator op(argument_list){
    // operator logic
}

Operators That CAN be Overloaded

✓ +   -   *   /   %   ==   !=   <   >   ++   --   <<   >>

Operators That CANNOT be Overloaded

✗ . (dot)   :: (scope resolution)   sizeof   ?: (ternary)   .* (member pointer)

Key Rules

✓ Only existing operators can be overloaded — cannot create new ones
✓ At least one operand must be user-defined type
✓ Cannot change operator precedence or associativity

Complete Program — Overloading ++ (Unary)

#include<iostream>
using namespace std;
class Test{
private:
    int num;
public:
    Test(){ num=8; }
    void operator++(){   // overloading prefix ++
        num=num+2;
    }
    void Print(){ cout<<"The Count is: "<<num<<endl; }
};
int main(){
    Test tt;
    ++tt;             // calls operator++() function
    tt.Print();
    return 0;
}
► Expected Output
The Count is: 10

Complete Program — Overloading + (Binary Operator)

Binary operator takes one argument (the right-hand operand). The left-hand object is the calling object (this).

#include<iostream>
using namespace std;
class Distance{
public:
    int metres;
    Distance(int m){ metres=m; }
    Distance operator+(Distance d2){   // overload binary +
        Distance temp(0);
        temp.metres = metres + d2.metres;
        return temp;
    }
    void display(){ cout<<"Distance: "<<metres<<" m"<<endl; }
};
int main(){
    Distance d1(50), d2(30);
    Distance d3 = d1+d2;   // calls d1.operator+(d2)
    d1.display();
    d2.display();
    d3.display();
    return 0;
}
► Expected Output
Distance: 50 m Distance: 30 m Distance: 80 m
Key Insightd1 + d2 is translated by compiler to d1.operator+(d2). The left object (d1) is the calling object, the right object (d2) is the argument.
Exam Answer — 5 marks

Operator overloading allows C++ operators to have new meanings for user-defined types. It is compile-time polymorphism. Syntax: return_type operator op(args){ }. Operators that cannot be overloaded: ., ::, sizeof, ?:. Rules: Only existing operators can be overloaded; at least one operand must be user-defined; precedence and associativity cannot change.

#include <iostream>
using namespace std;
class Complex {
public:
    double real, imag;
    Complex(double r=0, double i=0){ real=r; imag=i; }
    Complex operator+(Complex c){
        return Complex(real+c.real, imag+c.imag);
    }
    bool operator==(Complex c){
        return (real==c.real && imag==c.imag);
    }
    void display(){ cout<<real<<"+"<<imag<<"i"<<endl; }
};
int main(){
    Complex c1(3,4), c2(1,2);
    Complex c3 = c1+c2;
    c1.display(); c2.display(); c3.display();
    cout<<"Equal? "<<(c1==c2 ? "Yes":"No")<<endl;
    return 0;
}
► Expected Output
3+4i 1+2i 4+6i Equal? No
1
operator+ is a member function. c1+c2 is translated by compiler to c1.operator+(c2).
2
Left operand (c1) is the calling object; right operand (c2) is the argument c.
3
Returns a NEW Complex object: Complex(real+c.real, imag+c.imag) = Complex(4,6).
4
operator== returns bool. Checks both real and imag parts match.
5
Cannot overload: . :: sizeof ?: — these are fixed by the language.
class Complex { public: int r, i; Complex operator+(Complex c){ Complex t; t.r = r+c.r; t.i = i+c.i; return t; } };
► Expected Output
1
Use operator keyword followed by symbol.
2
Redefines symbol behavior for objects.
3
c1+c2 becomes c1.operator+(c2).
Memory Lockoperator keyword + symbol = overloaded operator function
CANNOT overload: . :: sizeof ?: .*
Compile-time polymorphism

06

Function Overriding & Virtual Functions

Real-Life AnalogyA company policy says "greet customers". Each branch (child) greets differently — one bows, one shakes hands, one waves. Same instruction (method), different action per branch. That is overriding.

Function Overriding — Definition

When a derived class defines the same function as in the base class with the same name and signature, it overrides the base class function. Used for run-time polymorphism.

Key Idea

✓ Base class pointer points to derived class object
virtual keyword in base class enables run-time polymorphism
✓ Without virtual, base class version is always called (no override)

Complete Program — Without virtual (NO overriding)

#include<iostream>
using namespace std;
class Animal{
public:
    void speak(){ cout<<"Animal speaks"<<endl; }
};
class Dog:public Animal{
public:
    void speak(){ cout<<"Dog barks"<<endl; }
};
int main(){
    Animal* p = new Dog();
    p->speak();   // calls Animal::speak — NOT Dog (no virtual)
    return 0;
}
► Output (without virtual)
Animal speaks

Complete Program — With virtual (TRUE overriding)

#include<iostream>
using namespace std;
class Animal{
public:
    virtual void speak(){ cout<<"Animal speaks"<<endl; }
};
class Dog:public Animal{
public:
    void speak(){ cout<<"Dog barks"<<endl; }
};
class Cat:public Animal{
public:
    void speak(){ cout<<"Cat meows"<<endl; }
};
int main(){
    Animal* p;
    Dog d; Cat c;
    p=&d; p->speak();   // Dog barks
    p=&c; p->speak();   // Cat meows
    return 0;
}
► Expected Output
Dog barks Cat meows
1
virtual in base class tells C++: "when this is called on a base pointer, look at the actual object's type and call its version."
2
Without virtual: base version always called (compile-time binding). With virtual: correct version called based on actual object (run-time binding).

Important Terms — Late Binding & Dynamic Binding

Early Binding (Static / Compile-time)

The function to be called is decided at compile time. Happens with normal (non-virtual) functions and function overloading. Also called static binding.

Late Binding (Dynamic / Run-time)

The function to be called is decided at run time, based on the actual type of object the pointer points to. Happens with virtual functions. Also called dynamic binding or late binding.

Term Meaning Achieved by
Early Binding Function resolved at compile time Normal functions, overloading
Late Binding Function resolved at run time virtual keyword
Dynamic Binding Same as Late Binding virtual keyword
Static Binding Same as Early Binding Non-virtual function calls
Exam TipLate binding = Dynamic binding = Run-time binding. All three terms mean the same thing and are achieved using virtual functions. Early binding = Static binding = Compile-time binding.
Exam Answer — 5 marks

Function overriding occurs when a derived class redefines a base class function with the same name and signature. The base class function must be declared virtual for run-time polymorphism. Without virtual, the base class version is called even through a derived class pointer. With virtual, the correct derived class version is called based on the actual object at run-time. This is called run-time or dynamic polymorphism.

#include <iostream>
using namespace std;
class Animal {
public:
    virtual void sound(){ cout<<"Some animal sound"<<endl; }
    void breathe(){ cout<<"Animal breathes"<<endl; }
};
class Dog : public Animal {
public:
    void sound() override { cout<<"Dog: Woof!"<<endl; }
};
class Cat : public Animal {
public:
    void sound() override { cout<<"Cat: Meow!"<<endl; }
};
int main(){
    Animal* arr[3];
    arr[0] = new Animal();
    arr[1] = new Dog();
    arr[2] = new Cat();
    for(int i=0; i<3; i++){
        arr[i]->sound();      // runtime polymorphism
        arr[i]->breathe();    // not overridden - base version
    }
    return 0;
}
► Expected Output
Some animal sound Animal breathes Dog: Woof! Animal breathes Cat: Meow! Animal breathes
1
virtual void sound() in Animal: marks it overridable. Without virtual, base version always called via pointer.
2
override keyword in Dog/Cat: tells compiler 'I am intentionally overriding'. Gives error if signature doesn't match base.
3
Animal* arr[3]: array of base pointers. Can hold Animal, Dog, Cat objects.
4
arr[i]->sound(): at runtime, C++ checks actual object type via vptr/vtable and calls correct version.
5
arr[i]->breathe(): NOT virtual, NOT overridden → always calls Animal::breathe(). Early binding.
class Shape { public: virtual void draw(){ cout<<"S"; } };
class Circle : public Shape { public: void draw(){ cout<<"C"; } };
► Expected Output
1
Same method in base and derived class.
2
Requires virtual keyword in base class.
3
Runtime polymorphism via base pointer.
Memory LockOverriding = same name + same params in derived class
virtual = enables run-time polymorphism
Without virtual = compile-time (wrong function called via base pointer)

07

UML Sequence Diagram

Real-Life AnalogyA call centre script: Customer calls → Agent picks up → Agent checks account → Agent resolves issue → Call ends. A sequence diagram captures exactly this — who talks to whom, in what order, over time.

What is a UML Interaction Diagram?

In UML, Interaction Diagrams show how objects communicate with each other to carry out a specific functionality. They focus on message flow, order of execution, and object collaboration.

Two Types of Interaction Diagrams

1. Sequence Diagram — emphasizes time sequence of messages
2. Collaboration Diagram — emphasizes structural organization of objects

Sequence Diagram — Definition

A Sequence Diagram shows how objects interact in a specific order over time by exchanging messages. It focuses on WHEN and IN WHAT ORDER messages are passed.

Purpose

✓ Represents object interactions in time sequence
✓ Shows order of message flow between objects
✓ Helps understand dynamic behavior of a system
✓ Describes how a use case is executed
✓ Helps in system design, analysis, and debugging

Components (Notations)

Component Symbol Meaning
Actor Stick figure External entity interacting with the system
Lifeline Vertical dashed line below object box Represents object's existence over time
Activation Bar Thin rectangle on lifeline Shows when object is active/executing
Synchronous Message Solid arrow → Sender waits for reply before continuing
Asynchronous Message Open arrow → Sender does NOT wait for reply
Return Message Dashed arrow <-- Response returned to the caller
Self Message Arrow looping back to same lifeline Object calls itself
Create Message Dashed arrow to new object Creates a new object/lifeline
Destroy Message Arrow with X at end Destroys an object

Steps to Create a Sequence Diagram (Exam Guide)

1
Identify the Scenario — understand the use case to represent
2
List Participants — objects, actors, and systems involved
3
Define Lifelines — draw vertical dashed line for each participant
4
Arrange Lifelines — position horizontally in order of involvement
5
Add Activation Bars — draw on sending participant's lifeline for each message
6
Draw Messages — arrows between lifelines showing communication
7
Include Return Messages — dashed arrows back to caller
8
Indicate Timing/Order — number messages (1, 2, 3...)
9
Add Conditions/Loops — use combined fragments (alt, loop, opt)

Example — ATM Cash Withdrawal

Scenario: Customer withdraws cash from ATM

Objects Involved: Customer (Actor), ATM, Bank Server

Customer          ATM               Bank Server
   |                |                    |
   |--insertCard--->|                    |
   |--enterPIN----->|                    |
   |                |---verifyPIN------->|
   |                |<--PINValid---------|
   |--requestCash-->|                    |
   |                |---checkBalance---->|
   |                |<--balanceSufficient|
   |                |---dispenseCash---->|
   |<--receiveCash--|                    |
   |                |                    |

How to Write Sequence Diagram in Exam

Exam Writing Format — Step by Step
1
Write the heading: "Sequence Diagram for [scenario name]"
2
List objects horizontally as labelled boxes (e.g., [Customer], [ATM], [BankServer])
3
Draw vertical dashed lines (lifelines) below each object box
4
Draw horizontal arrows between lifelines with message labels. Use solid arrows for calls, dashed for returns
5
Number each message in sequential order (1, 2, 3...)
6
Add thin rectangles (activation bars) on lifelines to show active periods

Template answer (5 marks): Define → List components/notations (4-5 points) → State purpose → Draw or describe ATM example → Mention benefits vs drawbacks

How to Write Collaboration Diagram in Exam

Exam Writing Format — Collaboration Diagram
1
Write heading: "Collaboration Diagram for [scenario]"
2
Draw objects as rectangles arranged in a network (not top-to-bottom like sequence)
3
Draw lines (links) between connected objects
4
Add numbered arrows along the links showing message flow (1:msg, 2:msg, ...)
5
Key difference: Numbers must be written explicitly (not implied by vertical position)

Template answer (5 marks): Define → Compare with Sequence Diagram → List notations → Describe/draw ATM example → List benefits

Benefits and Drawbacks

Benefits Drawbacks
Explores real-time application flow Too many lifelines makes it complex
Depicts message flow between objects Changing message order produces incorrect results
Easy to update on system change Each sequence needs distinct notations
Supports forward and reverse engineering Complexity grows with system size
Exam Answer — 5 marks

A Sequence Diagram is a UML interaction diagram that shows how objects interact with each other in a specific order over time by exchanging messages. It focuses on the time sequence (WHEN and IN WHAT ORDER) of messages. Key notations: Lifeline (vertical dashed line), Actor (stick figure), Activation Bar (rectangle), Synchronous Message (solid arrow), Return Message (dashed arrow). Example: ATM withdrawal scenario shows Customer→ATM→Bank Server message flow. Purpose: to visualize and design dynamic behavior of a system.

class A { public: void callB(B &obj){ obj.hello(); } };
class B { public: void hello(){ cout<<"Hi"; } };
► Expected Output
Hi
1
Shows how objects communicate over time.
2
Object A sends message (calls method) to Object B.
3
Sequence flows top to bottom.
Memory LockSequence Diagram = WHO talks to WHOM in WHAT ORDER
Lifeline = dashed vertical line | Arrow = message
Emphasizes TIME sequence

08

Collaboration Diagram

Real-Life AnalogyAn org chart shows who is connected to whom in a company. A collaboration diagram does the same for objects — it shows WHICH objects are linked, with numbered messages showing the flow.

Definition

A Collaboration Diagram (also called Communication Diagram) is a UML interaction diagram that shows how objects interact through messages, emphasizing structural organization and relationships among objects — rather than time sequence.

Purpose

✓ Show object collaboration in a system
✓ Emphasize links/associations between objects
✓ Understand object responsibilities
✓ Represent message flow using numbered arrows

Components (Notations)

Component Symbol Meaning
Object/Participant Rectangle with name An object participating in the interaction
Multiple Objects Stacked rectangles Multiple instances of same class
Actor Stick figure External entity involved
Message Numbered arrow between objects Communication between objects
Self Message Arrow looping to same object Object calls itself
Link Line connecting objects Association/relationship between objects
Return Message Dashed arrow Response sent back to caller

Steps to Draw Collaboration Diagram

1
Identify Objects/Participants — classes, actors, modules involved
2
Define Interactions — determine how objects work together
3
Add Messages — draw numbered arrows between objects
4
Consider Relationships — show dependencies with dashed lines or arrows
5
Document — add notes and explanations as needed

Sequence vs Collaboration Diagram

Feature Sequence Diagram Collaboration Diagram
Focus Time sequence / order of messages Structural organization / object links
Layout Vertical (top to bottom) Network / graph (objects as nodes)
Message numbering Implied by vertical position Explicit numbers (1, 2, 3...)
Best for Understanding flow over time Understanding object relationships

Example — ATM Balance Enquiry (Collaboration)

[Customer] ---1: insertCard---> [ATM]
[ATM]      ---2: requestPIN---> [Customer]
[Customer] ---3: enterPIN-----> [ATM]
[ATM]      ---4: verifyPIN----> [BankServer]
[BankServer]---5: sendBalance-> [ATM]
[ATM]      ---6: displayBalance->[Customer]

Numbers indicate message order. Objects shown as rectangles, arrows show message direction.

Benefits of Collaboration Diagrams

✓ Simplifies how system components interact
✓ Enhances discussion and decision-making
✓ Helps visualize data and control flow
✓ Aids in debugging by showing interaction sequence and error sources

Exam Answer — 5 marks

A Collaboration Diagram (Communication Diagram) is a UML interaction diagram showing how objects are connected and exchange messages, emphasizing structural relationships. Objects are shown as rectangles, links as lines, and messages as numbered arrows. Unlike Sequence Diagram (which focuses on time order), Collaboration Diagram focuses on object structure. Steps: Identify objects → Define interactions → Add numbered messages → Show relationships → Document. Example: ATM Balance Enquiry shows Customer, ATM, and BankServer exchanging numbered messages.

class Printer { public: void print(){ cout<<"Printing"; } };
class User { public: void use(Printer &p){ p.print(); } };
► Expected Output
Printing
1
Focuses on relationships (links) between objects.
2
Numbered messages show flow of control.
3
Similar to sequence diagram but spatial.
Memory LockCollaboration = WHICH objects connected + numbered messages
vs Sequence = time order (top to bottom)
Collaboration emphasizes STRUCTURE | Sequence emphasizes TIME

Input-Based Programs — Methods & Polymorphism

Program 1 — Constructor with User Input
#include <iostream>
using namespace std;

class Employee {
    string name;
    int id;
public:
    Employee(string n, int i) {
        name = n;
        id = i;
    }
    void display() {
        cout << "Employee: " << name << ", ID: " << id << "\n";
    }
};

int main() {
    string empName;
    int empId;
    cout << "Enter Name: ";
    cin >> empName;
    cout << "Enter ID: ";
    cin >> empId;
    
    Employee e1(empName, empId);
    e1.display();
    return 0;
}

Output Traps — Advanced

🧠 Trap 1: Virtual Destructor Missing

What happens when you delete a derived object through a base pointer if the base destructor is NOT virtual?

Only the Base destructor is called. The Derived destructor is skipped, leading to a memory leak!

🧠 Trap 2: Object Passing by Value

What gets called when an object is passed by value to a function?

The Copy Constructor is called to create a temporary copy. If not defined, the default shallow copy constructor is used.


Write-From-Scratch Exam Guides

How to write Operator Overloading
1
Define a class with private data members.
2
Create a constructor to initialize them.
3
Define the operator function: ReturnType operator+(const ClassName& obj)
4
Inside, create a temporary object, perform addition of this->member + obj.member, and return it.
5
In main(), test it with C = A + B;

P1

Practice — Overloading & Overriding

✍️ Write From Scratch
Basic — 5 Marks
Write a class Box with private length and width (float). Overload the + operator to add two Box objects (add their dimensions). Include a constructor and display() method. Test in main.
#include<iostream>
using namespace std;
class Box{
    float length, width;
public:
    Box(float l=0, float w=0){ length=l; width=w; }
    Box operator+(Box b){
        return Box(length+b.length, width+b.width);
    }
    void display(){ cout<<"L:"<<length<<" W:"<<width<<"\n"; }
};
int main(){
    Box b1(3,4), b2(2,1);
    Box b3 = b1+b2;
    b3.display();
}
▶ Output
L:5 W:5
Medium — 8 Marks
Create class Complex with real and imag (float). Overload: + (add two complex numbers), == (compare equality), and << (print as "a+bi"). Test all three in main.
#include<iostream>
using namespace std;
class Complex{
    float real, imag;
public:
    Complex(float r=0,float i=0){ real=r; imag=i; }
    Complex operator+(Complex c){ return Complex(real+c.real, imag+c.imag); }
    bool operator==(Complex c){ return real==c.real && imag==c.imag; }
    friend ostream& operator<<(ostream& os, Complex c){
        os<<c.real<<"+"<<c.imag<<"i"; return os;
    }
};
int main(){
    Complex a(2,3), b(1,4);
    Complex c = a+b;
    cout<<c<<"\n";
    cout<<(a==b ? "Equal" : "Not Equal");
}
▶ Output
3+7i Not Equal
Tricky — 10 Marks
Demonstrate ALL types of constructor overloading in one class Vector: default constructor (0,0), parameterized (x,y), copy constructor. Also overload + and display(). Show constructor call order when creating: Vector v1, v2(3,4), v3=v2;.
#include<iostream>
using namespace std;
class Vector{
    float x, y;
public:
    Vector(){ x=0; y=0; cout<<"Default\n"; }
    Vector(float a,float b){ x=a; y=b; cout<<"Param\n"; }
    Vector(const Vector& v){ x=v.x; y=v.y; cout<<"Copy\n"; }
    Vector operator+(Vector v){ return Vector(x+v.x, y+v.y); }
    void display(){ cout<<"("<<x<<","<<y<<")\n"; }
};
int main(){
    Vector v1;
    Vector v2(3,4);
    Vector v3=v2;
    Vector v4=v1+v2;
    v4.display();
}
▶ Output
Default Param Copy Param Copy (3,4)
P2

Predict Output — Constructors & Virtual Dispatch

⚡ Predict the Output — Think Before You Click!
Trap 1 — Constructor & Destructor Order
class A{
public:
    A(){ cout<<"A()\n"; }
    ~A(){ cout<<"~A()\n"; }
};
class B:public A{
public:
    B(){ cout<<"B()\n"; }
    ~B(){ cout<<"~B()\n"; }
};
int main(){ B b; }

Output:
A()
B()
~B()
~A()

Constructors fire BASE first, then DERIVED. Destructors fire in REVERSE order: derived first, then base. This is always the rule.

Trap 2 — Early Binding (no virtual)
class Base{ public: void show(){ cout<<"Base\n"; } };
class Der:public Base{ public: void show(){ cout<<"Der\n"; } };
int main(){
    Base* p = new Der();
    p->show();
    delete p;
}

Output: Base
No virtual → static/early binding. Compiler decides at compile time based on pointer type (Base*), NOT the actual object. Add virtual to get "Der".

Trap 3 — Method Overloading Resolution
class Calc{
public:
    int   add(int a, int b){ return a+b; }
    float add(float a, float b){ return a+b; }
    int   add(int a, int b, int c){ return a+b+c; }
};
int main(){
    Calc c;
    cout<<c.add(2,3)<<"\n";
    cout<<c.add(1.5f,2.5f)<<"\n";
    cout<<c.add(1,2,3);
}

Output: 5 / 4 / 6
Compiler picks the overload based on argument types and count. add(2,3) → int version. add(1.5f,2.5f) → float version. add(1,2,3) → 3-param version.

Trap 4 — Copy Constructor vs Assignment
class A{
public:
    A(){ cout<<"Default\n"; }
    A(const A&){ cout<<"Copy\n"; }
    A& operator=(const A&){ cout<<"Assign\n"; return *this; }
};
int main(){
    A a;
    A b = a;
    A c;
    c = a;
}

Output:
Default
Copy
Default
Assign

A b = a at declaration → Copy constructor (object being CREATED from another). c = a after c exists → Assignment operator (both objects already exist).

P3

Debug This — Find & Fix the Bugs

🐛 Spot Every Bug. Fix It. Understand Why.
Bug 1 — Missing Virtual Destructor
class Base{
public:
    ~Base(){ cout<<"~Base\n"; }  // NOT virtual!
};
class Der:public Base{
public:
    ~Der(){ cout<<"~Der\n"; }
};
int main(){
    Base* p = new Der();
    delete p;  // BUG: only ~Base() called!
}

Bug: Destructor in Base is not virtual. When delete p is called on a Base*, only ~Base() fires. ~Der() is never called → memory/resource leak.
Fix: Change to virtual ~Base(){ cout<<"~Base\n"; }
Rule: Any class with virtual methods or used polymorphically MUST have a virtual destructor.

Bug 2 — Hiding Instead of Overriding (Signature Mismatch)
class Animal{
public:
    virtual void speak(int volume){ cout<<"Animal\n"; }
};
class Dog:public Animal{
public:
    void speak(){ cout<<"Dog\n"; }  // BUG
};
int main(){
    Animal* a = new Dog();
    a->speak(5);  // Calls Animal::speak, NOT Dog::speak!
}

Bug: Dog::speak() has a different signature (no int parameter). It HIDES Animal::speak(int) instead of overriding it. Virtual dispatch does NOT apply.
Fix: Match the signature exactly: void speak(int volume){ cout<<"Dog\n"; }
Tip: Always use override keyword — the compiler will catch mismatches: void speak(int volume) override { ... }

Bug 3 — Operator Overload Wrong Return Type
class Pt{
public:
    int x,y;
    Pt(int a,int b){ x=a; y=b; }
    void operator+(Pt p){  // BUG: returns void
        x+=p.x; y+=p.y;
    }
};
int main(){
    Pt a(1,2), b(3,4);
    Pt c = a+b;  // ERROR: can't assign void
}

Bug: Operator+ returns void so the result cannot be stored. Also mutates this, which is unexpected for +.
Fix:Pt operator+(Pt p){ return Pt(x+p.x, y+p.y); }
Rule: Arithmetic operators should return a NEW object, not modify this. Assignment operators (+=) may modify this and return *this.

P4

Edge Cases You MUST Know

⚠️ Critical Edge Cases — Exam Traps

1. Calling Virtual Function from Constructor

class Base{
public:
    Base(){ show(); }  // calling virtual from constructor!
    virtual void show(){ cout<<"Base::show\n"; }
};
class Der:public Base{
public:
    void show() override{ cout<<"Der::show\n"; }
};
int main(){ Der d; }  // Output: "Base::show" NOT "Der::show"
Rule: During Base constructor, the Derived part is NOT yet constructed. VTable points to Base only. Virtual dispatch does NOT work inside constructors or destructors.

2. Operators That CANNOT Be Overloaded

// These operators can NEVER be overloaded:
::   (scope resolution)
.    (member access)
.*   (pointer-to-member)
?:   (ternary)
sizeof, typeid, alignof
Memory Aid: "Dot, Scope, Arrow-star, Ternary" cannot be overloaded. Everything else (including [], (), new, delete) can be overloaded.

3. Copy Constructor is Called by Value, Not Just Initialization

void func(MyClass obj){ // COPY constructor called here!
    // obj is a local copy
}
MyClass create(){ MyClass tmp; return tmp; } // Copy may be called on return
int main(){
    MyClass a;
    func(a);         // copy ctor called — passing by value
    MyClass b = create(); // copy may be elided (RVO)
}
Key: Pass by VALUE triggers copy constructor. Pass by REFERENCE does not. Always pass large objects by const& to avoid unnecessary copies.

4. Overriding Only Works via Pointer or Reference

class Animal{ public: virtual void speak(){ cout<<"Animal"; } };
class Dog:public Animal{ public: void speak() override{ cout<<"Dog"; } };
int main(){
    Animal a = Dog();  // SLICING — Dog part cut off → "Animal"
    Animal* p = new Dog(); // pointer → "Dog" (correct)
    Animal& r = *p;   // reference → "Dog" (correct)
    a.speak(); p->speak(); r.speak();
}
Object Slicing: Assigning a derived object to a base object by value slices off the derived portion. Always use pointer or reference for polymorphism.
P5

Active Recall — Test Yourself

🧠 Rapid-Fire Theory — Click to Reveal Answers
1. What is the difference between method overloading and method overriding?
Overloading: Same name, different parameters — resolved at compile time (same class). Overriding: Same name, same parameters — in derived class, resolved at runtime via virtual.
2. What is early binding vs late binding?
Early (static) binding: Compiler decides which function to call at compile time — no virtual. Late (dynamic) binding: Decision made at runtime via VTable — requires virtual keyword.
3. When is the copy constructor called (3 cases)?
1. Object initialized from another: A b = a;
2. Passing object by value to a function
3. Returning object by value from a function
4. What is the syntax to overload the + operator inside a class?
ClassName operator+(ClassName other){ return ClassName(member + other.member); }
Must return a new object. Takes the right-hand operand as parameter.
5. Why must the destructor be virtual in a polymorphic base class?
Without virtual, delete basePointer calls only the base destructor — the derived destructor is skipped, causing resource leaks. virtual ensures the correct destructor chain runs.
6. What is a sequence diagram? What does it show?
A UML interaction diagram showing the time-ordered sequence of messages between objects. X-axis = objects (lifelines). Y-axis = time (top to bottom). Arrows = method calls.
7. What is the difference between a sequence diagram and a collaboration diagram?
Sequence: Emphasizes time order — messages shown top to bottom with lifeline bars. Collaboration (Communication): Emphasizes object relationships — messages numbered, no lifelines, shows structural links.
8. Which operators cannot be overloaded in C++?
:: (scope), . (member access), .* (pointer-to-member), ?: (ternary), sizeof, typeid. All others can be overloaded.
9. What is constructor overloading? Give two examples.
Multiple constructors with different parameter lists in the same class.
1. Box(){ } — default
2. Box(float l, float w){ } — parameterized
3. Box(const Box& b){ } — copy
10. What does the override keyword do? Why use it?
override tells the compiler the function is intended to override a virtual base function. If the signature doesn't match exactly, the compiler gives an error — catching hiding bugs that would be silent otherwise.
P6

Final Exam Coding Programs

Question 1
Operator Overloading — Matrix Addition
12 Marks
Write a class Matrix with a 2x2 int array. Provide a parameterized constructor. Overload + to add two matrices and << (friend) to print the matrix. Create two Matrix objects, add them, and print the result.
#include<iostream>
using namespace std;
class Matrix{
    int m[2][2];
public:
    Matrix(int a,int b,int c,int d){ m[0][0]=a; m[0][1]=b; m[1][0]=c; m[1][1]=d; }
    Matrix operator+(Matrix o){
        return Matrix(m[0][0]+o.m[0][0], m[0][1]+o.m[0][1],
                      m[1][0]+o.m[1][0], m[1][1]+o.m[1][1]);
    }
    friend ostream& operator<<(ostream& os, Matrix x){
        os<<x.m[0][0]<<" "<<x.m[0][1]<<"\n"<<x.m[1][0]<<" "<<x.m[1][1]<<"\n";
        return os;
    }
};
int main(){
    Matrix a(1,2,3,4), b(5,6,7,8);
    Matrix c = a+b;
    cout<<c;
}
▶ Output
6 8 10 12
Question 2
Constructor Overloading + Destructor Order
10 Marks
Create class Product with: default constructor, parameterized constructor (name, price), copy constructor. Each constructor/destructor prints a message. Create objects: one default, one parameterized, one copy. Show exact output including destruction order.
#include<iostream>
using namespace std;
class Product{
    string name; float price;
public:
    Product(){ name="Unknown"; price=0; cout<<"Default: "<<name<<"\n"; }
    Product(string n, float p){ name=n; price=p; cout<<"Param: "<<name<<"\n"; }
    Product(const Product& p){ name=p.name; price=p.price; cout<<"Copy: "<<name<<"\n"; }
    ~Product(){ cout<<"~Destroy: "<<name<<"\n"; }
};
int main(){
    Product p1;
    Product p2("Laptop",999);
    Product p3=p2;
}
▶ Output
Default: Unknown Param: Laptop Copy: Laptop ~Destroy: Laptop ~Destroy: Laptop ~Destroy: Unknown
Question 3
UML Interaction Diagram — Describe & Draw (Theory)
8 Marks
Describe the Sequence Diagram and Collaboration Diagram for the following scenario: "A Customer places an Order. The Order system validates the item with the Inventory. If available, Inventory confirms stock to Order, and Order sends a confirmation to Customer." List all lifelines, messages, and numbering (for collaboration diagram).
📋 Sequence Diagram Answer
LifelinesCustomer | Order | Inventory
Message 1Customer → Order : placeOrder(item)
Message 2Order → Inventory : checkStock(item)
Message 3Inventory → Order : stockConfirmed(qty)
Message 4Order → Customer : sendConfirmation()
Draw RuleEach lifeline is a vertical dashed line. Messages are horizontal arrows (left to right = call, right to left = return). Time flows downward.
📋 Collaboration Diagram Answer
ObjectsCustomer, Order, Inventory (shown as boxes with links between them)
Message 11: placeOrder(item) [Customer → Order]
Message 22: checkStock(item) [Order → Inventory]
Message 33: stockConfirmed(qty) [Inventory → Order]
Message 44: sendConfirmation() [Order → Customer]
Key DiffNo lifelines. Messages numbered sequentially. Shows structural relationships (links), not time ordering.
P7

Complete Programs — Full Execution Flow

💻 Program 1 — Deep Polymorphism + Virtual Destructor
#include<iostream>
using namespace std;
class Shape{
protected: string color;
public:
    Shape(string c){ color=c; cout<<"Shape("<<c<<")\n"; }
    virtual float area()=0;
    virtual void describe(){ cout<<color<<" shape, area="<<area()<<"\n"; }
    virtual ~Shape(){ cout<<"~Shape("<<color<<")\n"; }
};
class Circle:public Shape{
    float r;
public:
    Circle(string c, float radius):Shape(c){ r=radius; cout<<"Circle(r="<<r<<")\n"; }
    float area() override{ return 3.14*r*r; }
    ~Circle(){ cout<<"~Circle\n"; }
};
class Rect:public Shape{
    float l,w;
public:
    Rect(string c,float a,float b):Shape(c){ l=a; w=b; cout<<"Rect(l="<<l<<" w="<<w<<")\n"; }
    float area() override{ return l*w; }
    ~Rect(){ cout<<"~Rect\n"; }
};
int main(){
    Shape* shapes[2];
    shapes[0] = new Circle("Red",7);
    shapes[1] = new Rect("Blue",4,5);
    for(int i=0;i<2;i++) shapes[i]->describe();
    for(int i=0;i<2;i++) delete shapes[i];
}
▶ Output
Shape(Red) Circle(r=7) Shape(Blue) Rect(l=4 w=5) Red shape, area=153.86 Blue shape, area=20 ~Circle ~Shape(Red) ~Rect ~Shape(Blue)
🧠 Step-by-Step Dry Run
new Circle("Red",7)Shape("Red") ctor fires first → then Circle ctor. Output: "Shape(Red)" then "Circle(r=7)"
shapes[0]→describe()Virtual dispatch → Circle::area() = 3.14×7×7 = 153.86. describe() in Shape uses overridden area()
delete shapes[0]virtual ~Shape → ~Circle() fires FIRST, then ~Shape(). Correct order because destructor is virtual
Without virtual ~Shapedelete shapes[0] would call ONLY ~Shape() → ~Circle() never runs → resource leak
▷ VTable at Runtime
shapes[0] VTablearea → Circle::area | describe → Shape::describe | destructor → ~Circle then ~Shape
shapes[1] VTablearea → Rect::area | describe → Shape::describe | destructor → ~Rect then ~Shape
💻 Program 2 — Operator Overloading: Complex Number Arithmetic
#include<iostream>
using namespace std;
class Complex{
    float r, i;
public:
    Complex(float a=0,float b=0):r(a),i(b){}
    Complex operator+(Complex c){ return Complex(r+c.r, i+c.i); }
    Complex operator-(Complex c){ return Complex(r-c.r, i-c.i); }
    Complex operator*(Complex c){ return Complex(r*c.r - i*c.i, r*c.i + i*c.r); }
    bool    operator==(Complex c){ return r==c.r && i==c.i; }
    Complex& operator=(const Complex& c){ r=c.r; i=c.i; return *this; }
    friend ostream& operator<<(ostream& os, Complex c){
        if(c.i>=0) os<<c.r<<"+"<<c.i<<"i";
        else       os<<c.r<<c.i<<"i";
        return os;
    }
};
int main(){
    Complex a(3,2), b(1,4);
    cout<<"a = "<<a<<"\n";
    cout<<"b = "<<b<<"\n";
    cout<<"a+b = "<<a+b<<"\n";
    cout<<"a-b = "<<a-b<<"\n";
    cout<<"a*b = "<<a*b<<"\n";
    cout<<"a==b? "<<(a==b?"Yes":"No")<<"\n";
    Complex c; c=a;
    cout<<"c = "<<c;
}
▶ Output
a = 3+2i b = 1+4i a+b = 4+6i a-b = 2-2i a*b = -5+14i a==b? No c = 3+2i
🧠 Dry Run: a*b where a=(3+2i), b=(1+4i)
Formula(a+bi)(c+di) = (ac-bd) + (ad+bc)i
Real partr*c.r - i*c.i = 3×1 - 2×4 = 3 - 8 = -5
Imag partr*c.i + i*c.r = 3×4 + 2×1 = 12 + 2 = 14
ResultComplex(-5, 14) → prints "-5+14i"
operator=c=a: assignment operator copies r=3, i=2 into c. Returns *this (reference to c). c prints "3+2i"

🚀 Live C++ Compiler