How unit 4 is examined
Polymorphism in C++: overloading, overriding, compile-time versus run-time binding, virtual functions, friend function and class, static functions. Marks sit on the compile-time versus run-time comparison, overloading versus overriding, and friend function.
Polymorphism: Introduction
<span style="display:inline-block;padding:.16em .6em;border:1.5px solid currentColor;border-radius:999px;font-size:.68em;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.75">High weight</span>
Definition. <mark>Polymorphism means "many forms": one name or interface that behaves differently depending on the type of object or the arguments it is used with.</mark>
Key points.
- Polymorphism is one of the four pillars of OOP, along with encapsulation, abstraction and inheritance.
- It has two kinds: compile-time (static) polymorphism, resolved by the compiler, and run-time (dynamic) polymorphism, resolved while the program runs.
- Compile-time polymorphism is achieved by function overloading and operator overloading.
- Run-time polymorphism is achieved by method overriding through virtual functions and a base-class pointer or reference.
- It lets one interface serve many types, so code such as
shape->draw()works for every present and future shape without change. - It makes programs extensible and readable, because new derived classes plug in without editing the calling code.
Example. The + operator adds integers, adds floats and joins strings; a draw() call paints a circle or a square depending on the object.
Answer frame. Open with the "many forms" definition; draw the small tree below; develop points 2-5; close with the extensibility benefit. For "short notes" write about half a page on each chosen item, using the exception handling and new/delete notes below.
<figure class="ds-fig" style="margin:1.4rem 0;overflow-x:auto"><svg xmlns="http://www.w3.org/2000/svg" id="dsfig-u4-01" viewBox="0 0 1410 194" width="1410" height="194" role="img" aria-label="Types of polymorphism"><style>#dsfig-u4-01 .e{stroke:#454C5A;stroke-width:1.4;fill:none}#dsfig-u4-01 .e.hi{stroke:#2340B8;stroke-width:2.6}#dsfig-u4-01 .n{fill:#FFFFFF;stroke:#16181D;stroke-width:1.4}#dsfig-u4-01 .n.hi{fill:#E3E9FC;stroke:#2340B8;stroke-width:2.2}#dsfig-u4-01 .n.rb-b{fill:#16181D;stroke:#16181D}#dsfig-u4-01 .n.rb-r{fill:#BD3227;stroke:#BD3227}#dsfig-u4-01 text{font-family:"JetBrains Mono",ui-monospace,Menlo,Consolas,monospace;font-size:13px}#dsfig-u4-01 .t{fill:#16181D;font-weight:500}#dsfig-u4-01 .t.inv{fill:#FFFFFF;font-weight:700}#dsfig-u4-01 .kd{stroke:#16181D;stroke-width:1.2}#dsfig-u4-01 .dot{fill:#16181D}#dsfig-u4-01 .ann{fill:#2340B8;font-size:11px;font-weight:700}#dsfig-u4-01 .lbl{fill:#6F7787;font-family:system-ui,-apple-system,sans-serif;font-size:12px;font-weight:700}#dsfig-u4-01 .ptr{fill:#2340B8;font-size:12px;font-weight:700}#dsfig-u4-01 .ah{fill:#454C5A}#dsfig-u4-01 .ah.hi{fill:#2340B8}#dsfig-u4-01 .wl rect{fill:#FFFFFF;stroke:#DCE0E7}#dsfig-u4-01 .wl .t{font-size:12px;font-weight:700}#dsfig-u4-01 .wl.hi rect{fill:#2340B8;stroke:#2340B8}#dsfig-u4-01 .wl.hi .t{fill:#FFFFFF}html.dark #dsfig-u4-01 .e{stroke:#B1B7C3}html.dark #dsfig-u4-01 .e.hi{stroke:#8FA3FF}html.dark #dsfig-u4-01 .n{fill:#161920;stroke:#E6E8ED}html.dark #dsfig-u4-01 .n.hi{fill:#1E2748;stroke:#8FA3FF}html.dark #dsfig-u4-01 .n.rb-b{fill:#E6E8ED;stroke:#E6E8ED}html.dark #dsfig-u4-01 .n.rb-r{fill:#FF7E71;stroke:#FF7E71}html.dark #dsfig-u4-01 .t{fill:#E6E8ED}html.dark #dsfig-u4-01 .t.inv{fill:#0F1115}html.dark #dsfig-u4-01 .kd{stroke:#E6E8ED}html.dark #dsfig-u4-01 .dot{fill:#E6E8ED}html.dark #dsfig-u4-01 .ann{fill:#8FA3FF}html.dark #dsfig-u4-01 .lbl{fill:#858D9C}html.dark #dsfig-u4-01 .ptr{fill:#8FA3FF}html.dark #dsfig-u4-01 .ah{fill:#B1B7C3}html.dark #dsfig-u4-01 .ah.hi{fill:#8FA3FF}html.dark #dsfig-u4-01 .wl rect{fill:#161920;stroke:#2A2E37}html.dark #dsfig-u4-01 .wl.hi rect{fill:#8FA3FF;stroke:#8FA3FF}html.dark #dsfig-u4-01 .wl.hi .t{fill:#0F1115}</style><defs><marker id="ah16" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah" d="M0,1 L9,5 L0,9 z"/></marker><marker id="ahh16" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah hi" d="M0,1 L9,5 L0,9 z"/></marker></defs><line class="e" x1="693" y1="37" x2="305" y2="101"/><line class="e" x1="693" y1="37" x2="1081" y2="101"/><line class="e" x1="305" y1="101" x2="111" y2="165"/><line class="e" x1="305" y1="101" x2="499" y2="165"/><line class="e" x1="1081" y1="101" x2="887" y2="165"/><line class="e" x1="1081" y1="101" x2="1275" y2="165"/><rect class="n" x="636" y="22" width="114" height="30" rx="8"/><text class="t" x="693" y="37" dy=".35em" text-anchor="middle">Polymorphism</text><rect class="n" x="213" y="86" width="184" height="30" rx="8"/><text class="t" x="305" y="101" dy=".35em" text-anchor="middle">Compile-time (static)</text><rect class="n" x="23" y="150" width="176" height="30" rx="8"/><text class="t" x="111" y="165" dy=".35em" text-anchor="middle">Function overloading</text><rect class="n" x="411" y="150" width="176" height="30" rx="8"/><text class="t" x="499" y="165" dy=".35em" text-anchor="middle">Operator overloading</text><rect class="n" x="1000.5" y="86" width="161" height="30" rx="8"/><text class="t" x="1081" y="101" dy=".35em" text-anchor="middle">Run-time (dynamic)</text><rect class="n" x="810.5" y="150" width="153" height="30" rx="8"/><text class="t" x="887" y="165" dy=".35em" text-anchor="middle">Virtual functions</text><rect class="n" x="1190.5" y="150" width="169" height="30" rx="8"/><text class="t" x="1275" y="165" dy=".35em" text-anchor="middle">Function overriding</text></svg><figcaption style="font-size:.82em;opacity:.72;margin-top:.45rem">Types of polymorphism</figcaption></figure>
Short note: Exception handling in C++. (Java's try-catch-finally is in Unit 5.)
- An exception is a run-time error such as division by zero; C++ handles it with
try(the risky code),throw(raises the exception) andcatch(handles it). - Several
catchblocks may follow onetry; the first whose type matches the thrown value runs, andcatch(...)catches any type, so it is written last. - Writing
throw;inside a catch block rethrows the same exception to an outer handler. - Stack unwinding: when an exception leaves a function, the destructors of all local objects of that function run before control reaches the matching catch.
- Advantages: error-handling code is kept apart from normal logic, an error propagates up the call stack to a caller that can handle it, and errors are grouped by type.
#include <iostream>
using namespace std;
double divide(int a, int b) {
if (b == 0) throw "Divide by zero";
if (b < 0) throw b;
return (double)a / b;
}
int main() {
try { cout << divide(10, 4) << endl; divide(10, 0); } // 2.5
catch (const char *m) { cout << m << endl; } // Divide by zero
catch (int e) { cout << "negative " << e; }
catch (...) { cout << "Unknown error"; }
}
Short note: new and delete.
newallocates memory on the heap at run time and returns a typed pointer:int *p = new int(5);, and for arraysint *a = new int[n];.delete p;frees a single object anddelete[] a;frees an array; mixing the two forms gives undefined behaviour.- Unlike
malloc,newis an operator, needs no size or type cast, and calls the constructor;deletecalls the destructor, whichfreedoes not. - If memory is not available,
newthrows thebad_allocexception, whereasmallocreturns NULL. - Forgetting
deletecauses a memory leak; using a pointer afterdeleteis a dangling pointer, so set it tonullptrafter freeing.
int *p = new int(5); int *a = new int[3]{1, 2, 3};
cout << *p << a[2]; // 53
delete p; delete[] a;
Asked: [14 marks] (Nov 2022, Jun 2024) Write short notes on (any two): i) Exception handling ii) New and delete iii) Polymorphism
Method Overriding & Overloading
<span style="display:inline-block;padding:.16em .6em;border:1.5px solid currentColor;border-radius:999px;font-size:.68em;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.75">High weight</span>
Definition. <mark>Overloading gives several functions in the same scope the same name but different parameter lists; overriding redefines a base-class function with the same signature in a derived class.</mark>
Key points.
- Overloading needs different number, type or order of parameters; the return type alone cannot distinguish overloads.
- Overloading is resolved by the compiler at compile time (early binding), so it is compile-time polymorphism.
- Overriding needs inheritance and an identical name, parameters and return type in the derived class.
- Overriding is resolved at run time (late binding) only when the base function is
virtualand is called through a base pointer or reference. - An inherited method is used unchanged from the base class; a redefined method replaces (hides) the base version with a new body in the derived class.
- Without
virtual, a redefined function is chosen by the pointer type, so the base version runs. - Operator overloading gives an operator a new meaning for a class using
return_type operator@(args). Unary operators as members take no argument (obj.operator-()); binary operators as members take one (a.operator+(b)). - As friends, operators take one more argument than as members: a unary friend takes one object and a binary friend takes two.
- Prefix
++is writtenP& operator++()and postfix++isP operator++(int), where the dummyintonly marks the postfix form. - Rules: operators that cannot be overloaded are
::,.,.*,?:andsizeof; new operator symbols cannot be created; precedence, associativity and the number of operands cannot change; at least one operand must be a user-defined type.
| Basis | Overloading | Overriding |
|---|---|---|
| Scope | Same class | Base and derived class |
| Signature | Must differ | Must be identical |
| Return type | May differ | Same (or covariant) |
| Binding | Early, compile time | Late, run time (with virtual) |
| Inheritance | Not needed | Required |
#include <iostream>
using namespace std;
class P { int x; public: P(int v=0) : x(v) {}
P operator-() { return P(-x); } // unary member
P operator+(P b) { return P(x + b.x); } // binary member
P& operator++() { ++x; return *this; } // prefix ++a
P operator++(int) { P t = *this; x++; return t; } // postfix a++
friend P operator*(P a, P b) { return P(a.x * b.x); } // binary friend: 2 args
friend P operator!(P a) { return P(a.x * 10); } // unary friend: 1 arg
void show() { cout << x << ' '; } };
int main() { P a(3), b(4);
(-a).show(); (a+b).show(); (a*b).show(); (!a).show(); // -3 7 12 30
(a++).show(); a.show(); (++a).show(); } // 3 4 5
Overloading and overriding, with calls:
```cpp
class Base { public: void f(int a) { cout << "int " << a; } // overloaded
void f(double a) { cout << "double " << a; }
virtual void show() { cout << "Base"; } };
class Derived : public Base { public: void show() { cout << "Derived"; } };
int main() { Derived d; Base *p = &d;
d.f(2); d.f(2.5); // int 2, double 2.5 (compile time)
p->show(); } // Derived (run time)
Inherited versus redefined method.
| Basis | Inherited method | Redefined method |
|---|---|---|
| Body | Base class body used unchanged | New body written in the derived class |
| Memory | One shared function for base and derived | A separate derived function besides the base one |
| Name lookup | Found in the base class | Found first in the derived class, hiding the base name |
Call d.show() |
Runs the base version | Runs the derived version |
Via Base*, non-virtual |
Base version | Base version (pointer type decides) |
Via Base*, virtual |
Base version | Derived version (object decides) |
class B { public: void a() { cout << "B::a "; } void s() { cout << "B::s "; }
void s(int) {} };
class D : public B { public: void s() { cout << "D::s "; } };
D d; d.a(); d.s(); // B::a (inherited) D::s (redefined)
// d.s(5) is an error: D::s hides every base s, including s(int)
<figure class="ds-fig" style="margin:1.4rem 0;overflow-x:auto"><svg xmlns="http://www.w3.org/2000/svg" id="dsfig-u4-02" viewBox="0 0 295 252" width="295" height="252" role="img" aria-label="D1 = derived class that inherits show(), so it calls the shared Base::show() (Bf); D2 = derived class that redefines show(), so it calls its own D2::show() (Df)"><style>#dsfig-u4-02 .e{stroke:#454C5A;stroke-width:1.4;fill:none}#dsfig-u4-02 .e.hi{stroke:#2340B8;stroke-width:2.6}#dsfig-u4-02 .n{fill:#FFFFFF;stroke:#16181D;stroke-width:1.4}#dsfig-u4-02 .n.hi{fill:#E3E9FC;stroke:#2340B8;stroke-width:2.2}#dsfig-u4-02 .n.rb-b{fill:#16181D;stroke:#16181D}#dsfig-u4-02 .n.rb-r{fill:#BD3227;stroke:#BD3227}#dsfig-u4-02 text{font-family:"JetBrains Mono",ui-monospace,Menlo,Consolas,monospace;font-size:13px}#dsfig-u4-02 .t{fill:#16181D;font-weight:500}#dsfig-u4-02 .t.inv{fill:#FFFFFF;font-weight:700}#dsfig-u4-02 .kd{stroke:#16181D;stroke-width:1.2}#dsfig-u4-02 .dot{fill:#16181D}#dsfig-u4-02 .ann{fill:#2340B8;font-size:11px;font-weight:700}#dsfig-u4-02 .lbl{fill:#6F7787;font-family:system-ui,-apple-system,sans-serif;font-size:12px;font-weight:700}#dsfig-u4-02 .ptr{fill:#2340B8;font-size:12px;font-weight:700}#dsfig-u4-02 .ah{fill:#454C5A}#dsfig-u4-02 .ah.hi{fill:#2340B8}#dsfig-u4-02 .wl rect{fill:#FFFFFF;stroke:#DCE0E7}#dsfig-u4-02 .wl .t{font-size:12px;font-weight:700}#dsfig-u4-02 .wl.hi rect{fill:#2340B8;stroke:#2340B8}#dsfig-u4-02 .wl.hi .t{fill:#FFFFFF}html.dark #dsfig-u4-02 .e{stroke:#B1B7C3}html.dark #dsfig-u4-02 .e.hi{stroke:#8FA3FF}html.dark #dsfig-u4-02 .n{fill:#161920;stroke:#E6E8ED}html.dark #dsfig-u4-02 .n.hi{fill:#1E2748;stroke:#8FA3FF}html.dark #dsfig-u4-02 .n.rb-b{fill:#E6E8ED;stroke:#E6E8ED}html.dark #dsfig-u4-02 .n.rb-r{fill:#FF7E71;stroke:#FF7E71}html.dark #dsfig-u4-02 .t{fill:#E6E8ED}html.dark #dsfig-u4-02 .t.inv{fill:#0F1115}html.dark #dsfig-u4-02 .kd{stroke:#E6E8ED}html.dark #dsfig-u4-02 .dot{fill:#E6E8ED}html.dark #dsfig-u4-02 .ann{fill:#8FA3FF}html.dark #dsfig-u4-02 .lbl{fill:#858D9C}html.dark #dsfig-u4-02 .ptr{fill:#8FA3FF}html.dark #dsfig-u4-02 .ah{fill:#B1B7C3}html.dark #dsfig-u4-02 .ah.hi{fill:#8FA3FF}html.dark #dsfig-u4-02 .wl rect{fill:#161920;stroke:#2A2E37}html.dark #dsfig-u4-02 .wl.hi rect{fill:#8FA3FF;stroke:#8FA3FF}html.dark #dsfig-u4-02 .wl.hi .t{fill:#0F1115}</style><defs><marker id="ah17" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah" d="M0,1 L9,5 L0,9 z"/></marker><marker id="ahh17" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah hi" d="M0,1 L9,5 L0,9 z"/></marker></defs><path class="e" d="M59,40 L234,40" marker-end="url(#ah17)"/><path class="e" d="M59,212 L234,212" marker-end="url(#ah17)"/><g class="wl"><rect x="113.2" y="31" width="68.7" height="18" rx="9"/><text class="t" x="147.5" y="40" dy=".35em" text-anchor="middle">inherits</text></g><g class="wl"><rect x="109.6" y="203" width="75.9" height="18" rx="9"/><text class="t" x="147.5" y="212" dy=".35em" text-anchor="middle">redefines</text></g><circle class="n" cx="40" cy="40" r="18"/><text class="t" x="40" y="40" dy=".35em" text-anchor="middle">D1</text><circle class="n" cx="40" cy="212" r="18"/><text class="t" x="40" y="212" dy=".35em" text-anchor="middle">D2</text><circle class="n" cx="255" cy="40" r="18"/><text class="t" x="255" y="40" dy=".35em" text-anchor="middle">Bf</text><circle class="n" cx="255" cy="212" r="18"/><text class="t" x="255" y="212" dy=".35em" text-anchor="middle">Df</text></svg><figcaption style="font-size:.82em;opacity:.72;margin-top:.45rem">D1 = derived class that inherits show(), so it calls the shared Base::show() (Bf); D2 = derived class that redefines show(), so it calls its own D2::show() (Df)</figcaption></figure>
Answer frame. Open with the definition of polymorphism; develop overloading then overriding with one snippet each; draw the table for "differentiate"; for operator overloading give syntax, unary and binary (member and friend), then close with the rules. For inherited versus redefined draw the diagram and the table and close with the virtual row.
Asked: [7 marks] (Dec 2023, Dec 2025) Differentiate between Overriding and Overloading methods. Asked: [7 marks] (Nov 2022, Dec 2025) Explain polymorphism with method overloading and overriding. Asked: [7 marks] (Nov 2022) How inherited method is different from redefined method? Asked: [7 marks] (Jun 2024) Define operator overloading? Explain how to overload unary operator and binary operator?
Pitfall: Changing only the return type is not overloading; it is a compile error.
static and run time Polymorphism
<span style="display:inline-block;padding:.16em .6em;border:1.5px solid currentColor;border-radius:999px;font-size:.68em;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.75">High weight</span>
Definition. <mark>Compile-time polymorphism binds a call to a function during compilation (early binding); run-time polymorphism binds it during execution through the object's vtable (late binding).</mark>
Key points.
- Compile-time polymorphism uses function and operator overloading, and the compiler picks the function from the argument types.
- Run-time polymorphism uses virtual functions: a base pointer or reference calls the derived version of an overridden function.
- For each class with virtual functions the compiler builds a vtable, a table of addresses of its virtual functions.
- Each object holds a hidden vptr pointing to its class's vtable, and the call
bp->f()looks up f through the vptr at run time. - Compile-time calls are faster because there is no lookup; run-time calls cost one extra pointer indirection.
- Run-time polymorphism is more flexible because the behaviour depends on the actual object, not the pointer type.
<figure class="ds-fig" style="margin:1.4rem 0;overflow-x:auto"><svg xmlns="http://www.w3.org/2000/svg" id="dsfig-u4-03" viewBox="0 0 467 80" width="467" height="80" role="img" aria-label="BP = Base* pointer, Obj = Derived object holding vptr, VT = Derived vtable, Fn = Derived::show()"><style>#dsfig-u4-03 .e{stroke:#454C5A;stroke-width:1.4;fill:none}#dsfig-u4-03 .e.hi{stroke:#2340B8;stroke-width:2.6}#dsfig-u4-03 .n{fill:#FFFFFF;stroke:#16181D;stroke-width:1.4}#dsfig-u4-03 .n.hi{fill:#E3E9FC;stroke:#2340B8;stroke-width:2.2}#dsfig-u4-03 .n.rb-b{fill:#16181D;stroke:#16181D}#dsfig-u4-03 .n.rb-r{fill:#BD3227;stroke:#BD3227}#dsfig-u4-03 text{font-family:"JetBrains Mono",ui-monospace,Menlo,Consolas,monospace;font-size:13px}#dsfig-u4-03 .t{fill:#16181D;font-weight:500}#dsfig-u4-03 .t.inv{fill:#FFFFFF;font-weight:700}#dsfig-u4-03 .kd{stroke:#16181D;stroke-width:1.2}#dsfig-u4-03 .dot{fill:#16181D}#dsfig-u4-03 .ann{fill:#2340B8;font-size:11px;font-weight:700}#dsfig-u4-03 .lbl{fill:#6F7787;font-family:system-ui,-apple-system,sans-serif;font-size:12px;font-weight:700}#dsfig-u4-03 .ptr{fill:#2340B8;font-size:12px;font-weight:700}#dsfig-u4-03 .ah{fill:#454C5A}#dsfig-u4-03 .ah.hi{fill:#2340B8}#dsfig-u4-03 .wl rect{fill:#FFFFFF;stroke:#DCE0E7}#dsfig-u4-03 .wl .t{font-size:12px;font-weight:700}#dsfig-u4-03 .wl.hi rect{fill:#2340B8;stroke:#2340B8}#dsfig-u4-03 .wl.hi .t{fill:#FFFFFF}html.dark #dsfig-u4-03 .e{stroke:#B1B7C3}html.dark #dsfig-u4-03 .e.hi{stroke:#8FA3FF}html.dark #dsfig-u4-03 .n{fill:#161920;stroke:#E6E8ED}html.dark #dsfig-u4-03 .n.hi{fill:#1E2748;stroke:#8FA3FF}html.dark #dsfig-u4-03 .n.rb-b{fill:#E6E8ED;stroke:#E6E8ED}html.dark #dsfig-u4-03 .n.rb-r{fill:#FF7E71;stroke:#FF7E71}html.dark #dsfig-u4-03 .t{fill:#E6E8ED}html.dark #dsfig-u4-03 .t.inv{fill:#0F1115}html.dark #dsfig-u4-03 .kd{stroke:#E6E8ED}html.dark #dsfig-u4-03 .dot{fill:#E6E8ED}html.dark #dsfig-u4-03 .ann{fill:#8FA3FF}html.dark #dsfig-u4-03 .lbl{fill:#858D9C}html.dark #dsfig-u4-03 .ptr{fill:#8FA3FF}html.dark #dsfig-u4-03 .ah{fill:#B1B7C3}html.dark #dsfig-u4-03 .ah.hi{fill:#8FA3FF}html.dark #dsfig-u4-03 .wl rect{fill:#161920;stroke:#2A2E37}html.dark #dsfig-u4-03 .wl.hi rect{fill:#8FA3FF;stroke:#8FA3FF}html.dark #dsfig-u4-03 .wl.hi .t{fill:#0F1115}</style><defs><marker id="ah18" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah" d="M0,1 L9,5 L0,9 z"/></marker><marker id="ahh18" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah hi" d="M0,1 L9,5 L0,9 z"/></marker></defs><path class="e" d="M59,40 L148,40" marker-end="url(#ah18)"/><path class="e" d="M188,40 L277,40" marker-end="url(#ah18)"/><path class="e" d="M317,40 L406,40" marker-end="url(#ah18)"/><g class="wl"><rect x="77.4" y="31" width="54.3" height="18" rx="9"/><text class="t" x="104.5" y="40" dy=".35em" text-anchor="middle">points</text></g><g class="wl"><rect x="213.1" y="31" width="40.8" height="18" rx="9"/><text class="t" x="233.5" y="40" dy=".35em" text-anchor="middle">vptr</text></g><g class="wl"><rect x="339" y="31" width="47.1" height="18" rx="9"/><text class="t" x="362.5" y="40" dy=".35em" text-anchor="middle">calls</text></g><circle class="n" cx="40" cy="40" r="18"/><text class="t" x="40" y="40" dy=".35em" text-anchor="middle">BP</text><circle class="n" cx="169" cy="40" r="18"/><text class="t" x="169" y="40" dy=".35em" text-anchor="middle">Obj</text><circle class="n" cx="298" cy="40" r="18"/><text class="t" x="298" y="40" dy=".35em" text-anchor="middle">VT</text><circle class="n" cx="427" cy="40" r="18"/><text class="t" x="427" y="40" dy=".35em" text-anchor="middle">Fn</text></svg><figcaption style="font-size:.82em;opacity:.72;margin-top:.45rem">BP = Base* pointer, Obj = Derived object holding vptr, VT = Derived vtable, Fn = Derived::show()</figcaption></figure>
#include <iostream>
using namespace std;
class Shape { public: virtual void draw() { cout << "Shape\n"; } };
class Circle : public Shape { public: void draw() { cout << "Circle\n"; } };
int main() {
Shape s; Circle c;
Shape *p = &s; p->draw(); // Shape
p = &c; p->draw(); // Circle (run time, via vtable)
}
Compile-time polymorphism by function and operator overloading (the compiler picks the function from the argument types):
#include <iostream>
using namespace std;
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
struct C { double re, im;
C operator+(C b) { return {re + b.re, im + b.im}; } };
int main() { C c = C{1, 2} + C{3, 4};
cout << add(2, 3) << ' ' << add(1.5, 2.5) << ' ' << c.re << '+' << c.im << 'i'; } // 5 4 4+6i
| Basis | Compile-time | Run-time |
|---|---|---|
| Binding time | Early, during compilation | Late, during execution |
| Achieved by | Function and operator overloading | Virtual functions and overriding |
| Function chosen from | Argument types and pointer type | Actual object type via vptr and vtable |
| Error detection | Mismatches caught at compile time | Wrong object type shows only at run time |
| Speed | Faster, direct call | Slightly slower, one extra lookup |
| Flexibility | Less | More |
| Inheritance | Not required | Required |
| Example | add(int,int), add(float,float) |
Shape* calling Circle::draw() |
Answer frame. Open with the definition of polymorphism; draw the vptr diagram and the program for "how achieved at run time"; then explain compile-time, run-time, vptr/vtable; end with the table for "compare"; close with the flexibility versus speed trade-off. For "differentiate", table first, then one example each.
Asked: [7 marks] (Nov 2022, Jun 2023, Jun 2024, Jun 2025, Dec 2025) Discuss and compare run time and compile time polymorphism; how is polymorphism achieved at compile time and run time? Asked: [7 marks] (Nov 2022, Dec 2023, Dec 2024, Dec 2025) Differentiate between compile-time and run-time polymorphism. Asked: [7 marks] (Dec 2023) Explain the meaning of polymorphism. How polymorphism is achieved at run time? Explain with an example.
Pitfall: Run-time polymorphism fails without
virtualor without a pointer or reference to the base.
Virtual Function
<span style="display:inline-block;padding:.16em .6em;border:1.5px solid currentColor;border-radius:999px;font-size:.68em;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.75">Medium weight</span>
Definition. <mark>A virtual function is a member function declared with the virtual keyword in a base class, so that a call through a base pointer runs the derived class's version.</mark>
Key points.
- Its purpose is to achieve run-time polymorphism through dynamic (late) binding.
- It works through the vtable and vptr: the call is resolved from the actual object's vtable, not the pointer type.
- Once virtual in the base, the function stays virtual in every derived class, even without the keyword.
- It must be a non-static member function; constructors cannot be virtual, but destructors should be virtual in a base class so
delete bpdestroys the derived object properly. - A pure virtual function (
virtual void f() = 0;) makes the class abstract, and derived classes must override it. - Rules: a virtual function cannot be static; it must be called through a base pointer or reference to get late binding (a call on an object is bound early); it can be a friend of another class; and its prototype must be identical in base and derived classes.
- Friend function and static function relate to class design as follows: a friend trades encapsulation for access, and a static function works on class-level data without an object (see below).
Virtual destructor. Deleting a derived object through a base pointer runs only ~Base() if the destructor is not virtual, so the derived part leaks; making it virtual runs ~Derived() then ~Base().
class Base { public: virtual ~Base() { cout << "~Base\n"; } };
class Der : public Base { public: ~Der() { cout << "~Der\n"; } };
Base *q = new Der;
delete q; // ~Der then ~Base; without virtual, only ~Base
Answer frame. Open with the definition; give the Shape/Circle program from the previous topic; explain the vtable in two lines; state the rules; close with the purpose. For the three-way question, add one paragraph each on friend and static, then the design guidelines under Static function.
Asked: [7 marks] (Dec 2024) Discuss virtual functions, friend functions and static functions in object-oriented programming, including their usage and implications for class design. Asked: [7 marks] (Jun 2024) Explain virtual function and write its purpose.
friend function
<span style="display:inline-block;padding:.16em .6em;border:1.5px solid currentColor;border-radius:999px;font-size:.68em;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.75">High weight</span>
Definition. <mark>A friend function is a non-member function declared inside a class with the friend keyword, which gives it access to the class's private and protected members.</mark>
Key points.
- It is declared inside the class as
friend return_type f(args);but defined outside withoutClassName::and without thefriendkeyword. - It is not a member, so it has no
thispointer and is called like an ordinary function; objects must be passed as arguments. - It is used to overload operators such as
<<and>>(whose left operand is a stream, not the class) and binary operators with a built-in left operand. - It is used to bridge two classes: one function can read the private data of both, for example to compare or add their values.
- Limitation: it breaks encapsulation and information hiding, because outside code touches private data directly.
- Security implication: a named non-member function gets unrestricted read and write access to the private data, so it can change any field without the class's checks; any function can be made friend by editing the class, which weakens data protection and raises coupling.
- Friendship is not inherited and not mutual; the class grants it, the friend cannot claim it.
- The assignment operator
=cannot be overloaded by a friend function. The compiler implicitly generates a member copy assignment operator for every class, so a friend version would clash with it or makea = bambiguous; the standard therefore requires=,(),[]and->to be non-static members.
Bridge between two classes (forward declaration, friend declared in both):
<figure class="ds-fig" style="margin:1.4rem 0;overflow-x:auto"><svg xmlns="http://www.w3.org/2000/svg" id="dsfig-u4-04" viewBox="0 0 252 252" width="252" height="252" role="img" aria-label="F = friend sum(), A and B = the two classes; the arrows are direct access to their private data"><style>#dsfig-u4-04 .e{stroke:#454C5A;stroke-width:1.4;fill:none}#dsfig-u4-04 .e.hi{stroke:#2340B8;stroke-width:2.6}#dsfig-u4-04 .n{fill:#FFFFFF;stroke:#16181D;stroke-width:1.4}#dsfig-u4-04 .n.hi{fill:#E3E9FC;stroke:#2340B8;stroke-width:2.2}#dsfig-u4-04 .n.rb-b{fill:#16181D;stroke:#16181D}#dsfig-u4-04 .n.rb-r{fill:#BD3227;stroke:#BD3227}#dsfig-u4-04 text{font-family:"JetBrains Mono",ui-monospace,Menlo,Consolas,monospace;font-size:13px}#dsfig-u4-04 .t{fill:#16181D;font-weight:500}#dsfig-u4-04 .t.inv{fill:#FFFFFF;font-weight:700}#dsfig-u4-04 .kd{stroke:#16181D;stroke-width:1.2}#dsfig-u4-04 .dot{fill:#16181D}#dsfig-u4-04 .ann{fill:#2340B8;font-size:11px;font-weight:700}#dsfig-u4-04 .lbl{fill:#6F7787;font-family:system-ui,-apple-system,sans-serif;font-size:12px;font-weight:700}#dsfig-u4-04 .ptr{fill:#2340B8;font-size:12px;font-weight:700}#dsfig-u4-04 .ah{fill:#454C5A}#dsfig-u4-04 .ah.hi{fill:#2340B8}#dsfig-u4-04 .wl rect{fill:#FFFFFF;stroke:#DCE0E7}#dsfig-u4-04 .wl .t{font-size:12px;font-weight:700}#dsfig-u4-04 .wl.hi rect{fill:#2340B8;stroke:#2340B8}#dsfig-u4-04 .wl.hi .t{fill:#FFFFFF}html.dark #dsfig-u4-04 .e{stroke:#B1B7C3}html.dark #dsfig-u4-04 .e.hi{stroke:#8FA3FF}html.dark #dsfig-u4-04 .n{fill:#161920;stroke:#E6E8ED}html.dark #dsfig-u4-04 .n.hi{fill:#1E2748;stroke:#8FA3FF}html.dark #dsfig-u4-04 .n.rb-b{fill:#E6E8ED;stroke:#E6E8ED}html.dark #dsfig-u4-04 .n.rb-r{fill:#FF7E71;stroke:#FF7E71}html.dark #dsfig-u4-04 .t{fill:#E6E8ED}html.dark #dsfig-u4-04 .t.inv{fill:#0F1115}html.dark #dsfig-u4-04 .kd{stroke:#E6E8ED}html.dark #dsfig-u4-04 .dot{fill:#E6E8ED}html.dark #dsfig-u4-04 .ann{fill:#8FA3FF}html.dark #dsfig-u4-04 .lbl{fill:#858D9C}html.dark #dsfig-u4-04 .ptr{fill:#8FA3FF}html.dark #dsfig-u4-04 .ah{fill:#B1B7C3}html.dark #dsfig-u4-04 .ah.hi{fill:#8FA3FF}html.dark #dsfig-u4-04 .wl rect{fill:#161920;stroke:#2A2E37}html.dark #dsfig-u4-04 .wl.hi rect{fill:#8FA3FF;stroke:#8FA3FF}html.dark #dsfig-u4-04 .wl.hi .t{fill:#0F1115}</style><defs><marker id="ah19" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah" d="M0,1 L9,5 L0,9 z"/></marker><marker id="ahh19" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="ah hi" d="M0,1 L9,5 L0,9 z"/></marker></defs><path class="e" d="M57,117.5 L193.2,49.4" marker-end="url(#ah19)"/><path class="e" d="M57,134.5 L193.2,202.6" marker-end="url(#ah19)"/><g class="wl"><rect x="102.5" y="74" width="47.1" height="18" rx="9"/><text class="t" x="126" y="83" dy=".35em" text-anchor="middle">reads</text></g><g class="wl"><rect x="102.5" y="160" width="47.1" height="18" rx="9"/><text class="t" x="126" y="169" dy=".35em" text-anchor="middle">reads</text></g><circle class="n" cx="40" cy="126" r="18"/><text class="t" x="40" y="126" dy=".35em" text-anchor="middle">F</text><circle class="n" cx="212" cy="40" r="18"/><text class="t" x="212" y="40" dy=".35em" text-anchor="middle">A</text><circle class="n" cx="212" cy="212" r="18"/><text class="t" x="212" y="212" dy=".35em" text-anchor="middle">B</text></svg><figcaption style="font-size:.82em;opacity:.72;margin-top:.45rem">F = friend sum(), A and B = the two classes; the arrows are direct access to their private data</figcaption></figure>
class B; // forward declaration
class A { int x; public: A(int v) : x(v) {} friend int sum(A, B); };
class B { int y; public: B(int v) : y(v) {} friend int sum(A, B); };
int sum(A a, B b) { return a.x + b.y; } // reads private data of both
int main() { cout << sum(A(5), B(7)); } // 12
Friend << and member =:
class X { int v; public: X(int a=0) : v(a) {}
friend ostream& operator<<(ostream& os, const X& o) { return os << o.v; }
X& operator=(const X& o) { if (this != &o) { v = o.v; } return *this; }
};
X a(3), b; b = a; cout << b; // 3
Answer frame. Open with the definition and syntax; give the bridge program; develop why and when (points 3-4) with the << example, then limitations (5-6); close with a use-friend-sparingly line. For the assignment question, define operator overloading (operator@), state the rules, answer "No" with the reason in point 8 and write the member operator=.
Asked: [14 marks] (Jun 2025) What is a friend function in C++? Why and when is it used? What are the limitations of friend functions in terms of encapsulation and security? Asked: [7 marks] (Nov 2022) What is Operator overloading? Can friend function be used to overload the assignment operator (=).
Pitfall: Writing
friendagain orA::when defining the friend outside the class.
Static function
<span style="display:inline-block;padding:.16em .6em;border:1.5px solid currentColor;border-radius:999px;font-size:.68em;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.75">Not asked since 2022</span>
Definition. <mark>A static member function belongs to the class rather than to any object, and is declared with the static keyword.</mark>
Key points.
- It is called without an object as
ClassName::f(). - It has no
thispointer, so it can access only static data members and other static functions. - It cannot be virtual (there is no object, so no vptr) or const (there is no
thisto make const), and it is used for counters and utility work shared by all objects. - A static data member is shared by all objects and defined once outside the class.
class C { static int n; public: C() { n++; } static int get() { return n; } };
int C::n = 0;
int main() { C a, b; cout << C::get(); } // 2
Class design guidelines. Give every polymorphic base class a virtual destructor; prefer member functions or a public interface over friends, and grant friendship only where an operator such as << needs it; use static members for data and counters shared by all objects.
friend class
<span style="display:inline-block;padding:.16em .6em;border:1.5px solid currentColor;border-radius:999px;font-size:.68em;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.75">Low weight</span>
Definition. <mark>A friend class is a class declared with friend class B; inside class A, so that all member functions of B can access A's private and protected members.</mark>
Key points.
- B's member functions access A's private and protected data directly through an A object, since A grants friendship to the whole class B.
- The implication is weaker encapsulation and tighter coupling between the two classes.
- Friendship is not mutual (A is not B's friend) and not transitive (a friend of a friend gets nothing), and it is not inherited.
- It suits closely tied classes such as a container and its iterator.
- The
friend class B;declaration may be placed in the private, protected or public section of A with the same effect.
class A { int p = 1; protected: int q = 2; friend class B; };
class B { public: void show(A &a) { cout << a.p << a.q; } };
int main() { A a; B b; b.show(a); } // 12
Asked: [7 marks] (Jun 2025) How does a friend class in C++ access the private and protected members of another class and what are the implications of this access?
Last-minute revision
- Polymorphism means "many forms": one interface, many behaviours; it is compile-time or run-time.
- Compile-time uses overloading with early binding; run-time uses virtual functions with late binding.
- Overloading needs different parameters in the same scope; overriding needs the same signature in a derived class.
- A vtable is the table of virtual function addresses per class; each object carries a vptr to it.
- Run-time polymorphism needs
virtualplus a base pointer or reference. - Operators that cannot be overloaded:
::,.,.*,?:,sizeof. - A friend function is a non-member with access to private members, has no
this, and is not inherited. =,(),[]and->cannot be overloaded through a friend function.- A static function has no
this, is called asClass::f(), and touches only static members. - Friendship is not mutual, not transitive and not inherited.
- Destructors of base classes should be virtual; constructors cannot be virtual.
newpairs withdelete,new[]withdelete[]; a failednewthrowsbad_alloc.
Memory hooks
- Overload = Over the same name, Order of params differs; Override = Over the base, Same signature.
- "V-P-T": Virtual, Pointer (vptr), Table (vtable).
- Compile = Early, Run = Late.
- Friend: "I give the key, you do not give it back."
- Static: "Class-owned, no this."
Coverage checklist
- Polymorphism: Introduction: short notes on polymorphism, exception handling, new and delete (14 marks).
- Method Overriding & Overloading: overriding vs overloading, polymorphism with both, inherited vs redefined, unary and binary operator overloading.
- static and run time Polymorphism: compare and differentiate compile-time and run-time, achieving run-time polymorphism.
- Virtual Function: virtual function and its purpose; virtual, friend and static together.
- friend function: friend function uses and limitations; friend and assignment operator.
- Static function: definition and rules (unasked).
- friend class: friend class access and implications.