In the previous chapter, we saw that Java supports run-time polymorphism (dynamic dispatch) automatically for all non-static, non-final methods. In C++, however, this behavior must be explicitly requested by the programmer using the virtual keyword.
virtual in C++In C++, if you use a base class pointer to call a method, the compiler uses the declared type of the pointer to decide which function to call (Static Binding), not the actual type of the object it points to.
class Animal {
public:
void speak() { cout << "Animal sound" << endl; }
};
class Dog : public Animal {
public:
void speak() { cout << "Woof!" << endl; }
};
Animal* ptr = new Dog();
ptr->speak(); // Output: "Animal sound" (WRONG! We wanted "Woof!")
Because speak() is not declared virtual, the compiler sees that ptr is of type Animal* and statically binds the call to Animal::speak(). It completely ignores the fact that ptr actually points to a Dog object.
virtual KeywordTo enable run-time polymorphism in C++, you must declare the method in the base class as virtual.
class Animal {
public:
virtual void speak() { cout << "Animal sound" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
};
Animal* ptr = new Dog();
ptr->speak(); // Output: "Woof!" (CORRECT! Dynamic dispatch is now active.)
A Pure Virtual Function is a virtual function that has no implementation in the base class. It is declared by assigning = 0 to the function declaration. Any class containing at least one pure virtual function automatically becomes an Abstract Class and cannot be instantiated.
class Shape {
public:
virtual double area() = 0; // Pure virtual: NO implementation here
};
class Circle : public Shape {
public:
double radius;
double area() override {
return 3.14159 * radius * radius;
}
};
// Shape* s = new Shape(); // ERROR! Shape is abstract.
Shape* s = new Circle(); // OK. Circle provides the implementation.
If a base class has virtual functions, its destructor should always be declared virtual. Otherwise, deleting a derived class object through a base class pointer will only call the base destructor, causing a memory leak.
class Base {
public:
virtual ~Base() { cout << "Base destroyed" << endl; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived destroyed" << endl; }
};
Base* ptr = new Derived();
delete ptr;
// Output: "Derived destroyed" then "Base destroyed" (CORRECT)
// Without virtual: only "Base destroyed" (MEMORY LEAK)
When you mark a method as virtual, the C++ compiler generates a hidden lookup table called a Virtual Table (V-Table) for each class that has virtual functions.
ptr->speak(), the compiled code does NOT jump directly to a hardcoded function address. Instead, it:
a. Follows ptr to the object.
b. Reads the hidden vptr from the object.
c. Uses the vptr to find the correct V-Table.
d. Looks up the function pointer for speak() in the V-Table.
e. Calls the function at that address.This extra level of indirection (pointer chasing) is why virtual function calls are slightly slower than regular function calls, but the flexibility they provide is well worth the tiny performance cost.