4 hours · 5 marks
A virtual function is a member function declared in a base class using the keyword virtual, which is expected to be redefined in derived classes. When called through a base class pointer/reference, the actual (derived) object's version is invoked — this is resolved at run time (late binding), not compile time.
class Shape {
public:
virtual void draw() { cout << "Drawing shape"; }
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing circle"; } // overrides base version
};
Without virtual, calling a function through a base class pointer always invokes the base class's version (early/static binding), regardless of the actual object type — this defeats runtime polymorphism.
Shape *s = new Circle();
s->draw(); // WITHOUT virtual: calls Shape::draw()
// WITH virtual: calls Circle::draw() -- correct runtime polymorphism
Virtual functions let a single interface (base class pointer) work correctly for objects of many derived types — the foundation of runtime polymorphism.
A base class pointer can point to a derived class object (upcasting is implicit and safe), but a derived class pointer cannot directly point to a base object without an explicit (and potentially unsafe) downcast.
Shape *sp;
Circle c;
sp = &c; // OK -- base pointer to derived object (upcasting)
sp->draw(); // calls Circle::draw() if draw() is virtual
// Circle *cp = sp; -> error, needs explicit cast
Circle *cp = static_cast<Circle*>(sp); // downcast (use dynamic_cast for safety)
A common OOP pattern: store an array of base class pointers, each pointing to a different derived object, then call the virtual function uniformly on each — every call automatically dispatches to the correct derived version.
Shape *shapes[3];
shapes[0] = new Circle();
shapes[1] = new Square();
shapes[2] = new Triangle();
for (int i = 0; i < 3; i++)
shapes[i]->draw(); // each call resolves to the correct derived draw()
A pure virtual function has no implementation in the base class and is declared with = 0. A class containing at least one pure virtual function becomes an abstract class — it cannot be instantiated directly, and serves only as a base for derived classes, which must override the pure virtual function(s) to become concrete (instantiable).
class Shape {
public:
virtual void draw() = 0; // pure virtual function
};
// Shape s; -> ERROR: cannot instantiate abstract class
class Circle : public Shape {
public:
void draw() override { cout << "Circle"; } // must override to be concrete
};
If a base class is meant to be used polymorphically (deleted through a base pointer), its destructor must be declared virtual. Otherwise, deleting a derived object through a base pointer invokes only the base destructor, causing the derived part's resources to leak (undefined behaviour).
class Base {
public:
virtual ~Base() { cout << "Base dtor"; } // virtual destructor
};
class Derived : public Base {
public:
~Derived() { cout << "Derived dtor"; }
};
Base *p = new Derived();
delete p; // with virtual ~Base(): calls Derived dtor then Base dtor (correct)
// without virtual: would call ONLY Base dtor -- leak/bug
reinterpret_cast performs a low-level reinterpretation of the bit pattern of an object — e.g., converting a pointer of one unrelated type to another, or a pointer to an integer. It performs no type checking or conversion logic and should be used sparingly since it can produce unsafe/undefined results if misused.
int a = 65;
int *p = &a;
char *c = reinterpret_cast<char*>(p); // reinterprets the pointer's bit pattern
RTTI allows the type of an object to be determined during program execution. C++ provides two RTTI facilities:
typeid operator — returns a type_info object identifying an expression's type: cout << typeid(*ptr).name();dynamic_cast — safely converts a base class pointer/reference to a derived type, returning nullptr (for pointers) or throwing std::bad_cast (for references) if the conversion is invalid. Requires the base class to be polymorphic (have at least one virtual function).Shape *s = new Circle();
Circle *c = dynamic_cast<Circle*>(s);
if (c) cout << "Successfully cast to Circle";
else cout << "Cast failed";
virtual that is overridden in derived classes; calling it through a base pointer/reference invokes the actual object's version at run time.
class Animal {
public:
virtual void sound() { cout << "Some sound"; }
};
class Dog : public Animal {
public:
void sound() override { cout << "Bark"; }
};
class Cat : public Animal {
public:
void sound() override { cout << "Meow"; }
};
int main() {
Animal *a;
Dog d; Cat c;
a = &d; a->sound(); // Bark
a = &c; a->sound(); // Meow
}
= 0 and has no body in the base class. A class with at least one pure virtual function is abstract. It cannot be instantiated because it is logically incomplete — some of its behaviour is undefined until a derived class supplies an implementation; the compiler enforces this to prevent calling an undefined function. See section 6.5.dynamic_cast and its use in run-time type identification, with an example. 5 marksdynamic_cast safely converts a pointer/reference from a base class type to a derived class type at run time, checking the actual object type; it returns nullptr for an invalid pointer conversion (or throws bad_cast for references). It requires the base class to be polymorphic. See code in section 6.8.Shape base class with a pure virtual draw(), derive Circle, Square, Triangle, store their pointers in a Shape* array, and loop calling draw() on each; each call correctly dispatches to the matching derived class implementation.typeid operator used for? 3 markstypeid returns type information about an expression at run time (part of RTTI), commonly used to identify the actual dynamic type of an object accessed through a base class pointer/reference, e.g. typeid(*ptr).name() or to compare types with typeid(a) == typeid(b).