C++ Function Overriding: Complete Guide with Examples
C++ Function Overriding: Complete Guide with Examples
Function overriding is a fundamental concept in C++ object-oriented programming that allows derived classes to provide specific implementations of functions defined in base classes. This guide covers all aspects of function overriding with practical examples.
What is Function Overriding?
Function overriding occurs when a derived class defines a function with the same name, return type, and parameters as a function in its base class. The derived class function “overrides” the base class function, providing a new implementation.
Basic Example
#include <iostream>
class Base {
public:
void display() {
std::cout << "Base class display()" << std::endl;
}
void show(int x) {
std::cout << "Base class show(): " << x << std::endl;
}
};
class Derived : public Base {
public:
// Override display()
void display() {
std::cout << "Derived class display()" << std::endl;
}
// Override show()
void show(int x) {
std::cout << "Derived class show(): " << x << std::endl;
}
};
int main() {
Base base;
Derived derived;
base.display(); // Output: Base class display()
derived.display(); // Output: Derived class display()
base.show(10); // Output: Base class show(): 10
derived.show(20); // Output: Derived class show(): 20
return 0;
}
Function Overriding vs Function Hiding
Function Hiding (Without Virtual)
When a base class function is not virtual, the derived class function hides it rather than overrides it. The function to call is determined at compile-time based on the pointer/reference type.
class Base {
public:
void func() {
std::cout << "Base::func()" << std::endl;
}
};
class Derived : public Base {
public:
void func() {
std::cout << "Derived::func()" << std::endl;
}
};
int main() {
Derived derived;
Base* basePtr = &derived;
derived.func(); // Output: Derived::func()
basePtr->func(); // Output: Base::func() (hiding, not overriding)
return 0;
}
Function Overriding (With Virtual)
When a base class function is virtual, the derived class function overrides it. The function to call is determined at runtime based on the actual object type.
class Base {
public:
virtual void func() {
std::cout << "Base::func()" << std::endl;
}
};
class Derived : public Base {
public:
void func() override { // C++11: override keyword
std::cout << "Derived::func()" << std::endl;
}
};
int main() {
Derived derived;
Base* basePtr = &derived;
derived.func(); // Output: Derived::func()
basePtr->func(); // Output: Derived::func() (runtime polymorphism)
return 0;
}
The override Keyword (C++11)
The override keyword explicitly indicates that a function is intended to override a virtual function from the base class. It provides compile-time checking and improves code clarity.
Benefits of override
- Compile-time checking: Compiler verifies that the function actually overrides a base class function
- Code clarity: Makes intent explicit
- Error prevention: Catches mistakes like typos or signature mismatches
class Base {
public:
virtual void func(int x) {
std::cout << "Base::func(int)" << std::endl;
}
virtual void process() const {
std::cout << "Base::process()" << std::endl;
}
};
class Derived : public Base {
public:
// Compile error: doesn't override (missing const)
// void process() override { } // Error!
// Correct override
void process() const override {
std::cout << "Derived::process()" << std::endl;
}
// Compile error: doesn't override (wrong parameter type)
// void func(double x) override { } // Error!
// Correct override
void func(int x) override {
std::cout << "Derived::func(int)" << std::endl;
}
};
Rules for Function Overriding
1. Function Signature Must Match
The function signature (name, parameters, const/volatile qualifiers) must match exactly.
class Base {
public:
virtual void func(int x) { }
virtual void process() const { }
};
class Derived : public Base {
public:
// ✅ Correct: exact match
void func(int x) override { }
// ✅ Correct: const matches
void process() const override { }
// ❌ Wrong: different parameter type (hiding, not overriding)
void func(double x) { }
// ❌ Wrong: missing const (hiding, not overriding)
void process() { }
};
2. Return Type Must Be Compatible
For non-virtual functions, return types must match exactly. For virtual functions, return types can be covariant (derived class can return a more derived type).
class Base {
public:
virtual Base* clone() {
return new Base(*this);
}
};
class Derived : public Base {
public:
// ✅ Covariant return type: Derived* is compatible with Base*
Derived* clone() override {
return new Derived(*this);
}
};
3. Access Specifier Can Differ
The access specifier (public, protected, private) can be different in the derived class, but this is generally not recommended.
class Base {
public:
virtual void func() { }
};
class Derived : public Base {
private:
// ⚠️ Allowed but not recommended
void func() override { }
};
4. Virtual Destructor Should Be Used
When overriding functions, the base class should have a virtual destructor to ensure proper cleanup.
class Base {
public:
virtual ~Base() { } // Virtual destructor
virtual void func() { }
};
class Derived : public Base {
public:
~Derived() { } // Destructor
void func() override { }
};
Practical Examples
Example 1: Shape Hierarchy
#include <iostream>
#include <cmath>
class Shape {
public:
virtual double area() const = 0; // Pure virtual
virtual void draw() const {
std::cout << "Drawing shape" << std::endl;
}
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) { }
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override {
std::cout << "Drawing circle with radius " << radius << std::endl;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) { }
double area() const override {
return width * height;
}
void draw() const override {
std::cout << "Drawing rectangle " << width << "x" << height << std::endl;
}
};
int main() {
Circle circle(5.0);
Rectangle rect(4.0, 6.0);
Shape* shapes[] = {&circle, &rect};
for (Shape* shape : shapes) {
shape->draw(); // Runtime polymorphism
std::cout << "Area: " << shape->area() << std::endl;
}
return 0;
}
Example 2: Animal Hierarchy
#include <iostream>
#include <string>
class Animal {
public:
virtual void makeSound() const {
std::cout << "Some generic animal sound" << std::endl;
}
virtual void move() const {
std::cout << "Animal moves" << std::endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "Woof! Woof!" << std::endl;
}
void move() const override {
std::cout << "Dog runs on four legs" << std::endl;
}
};
class Bird : public Animal {
public:
void makeSound() const override {
std::cout << "Chirp! Chirp!" << std::endl;
}
void move() const override {
std::cout << "Bird flies" << std::endl;
}
};
int main() {
Dog dog;
Bird bird;
Animal* animals[] = {&dog, &bird};
for (Animal* animal : animals) {
animal->makeSound();
animal->move();
}
return 0;
}
Common Pitfalls and Best Practices
Pitfall 1: Forgetting virtual in Base Class
class Base {
public:
void func() { } // ❌ Not virtual - will hide, not override
};
class Derived : public Base {
public:
void func() override { } // ❌ Error: no function to override
};
Solution: Always use virtual in base class for functions that should be overridable.
Pitfall 2: Signature Mismatch
class Base {
public:
virtual void func(int x) { }
};
class Derived : public Base {
public:
void func(double x) { } // ❌ Hiding, not overriding (different parameter type)
};
Solution: Use override keyword to catch these errors at compile-time.
Pitfall 3: Missing const Qualifier
class Base {
public:
virtual void process() const { }
};
class Derived : public Base {
public:
void process() { } // ❌ Hiding, not overriding (missing const)
};
Solution: Match all qualifiers exactly, including const, volatile, and noexcept.
Best Practices
- Always use
overridekeyword in C++11 and later - Use
virtualin base class for functions that should be overridable - Use virtual destructor in base class when using polymorphism
- Match function signatures exactly (name, parameters, qualifiers)
- Document intended overrides in base class documentation
- Use
finalkeyword to prevent further overriding when needed
The final Keyword (C++11)
The final keyword prevents a function from being overridden in derived classes.
class Base {
public:
virtual void func() { }
};
class Derived : public Base {
public:
void func() override final { } // Cannot be overridden further
};
class FurtherDerived : public Derived {
public:
// ❌ Error: cannot override final function
// void func() override { }
};
Summary
Function overriding is essential for runtime polymorphism in C++:
- Function overriding allows derived classes to provide specific implementations
- Use
virtualin base class to enable runtime polymorphism - Use
overridekeyword (C++11+) for compile-time checking and clarity - Match signatures exactly including const/volatile qualifiers
- Use virtual destructors in base classes with polymorphism
- Follow best practices to avoid common pitfalls
Understanding function overriding is crucial for effective object-oriented design in C++.