Question
In C++, I understand that a derived class can define a function with the same name as one in its base class, which seems like overriding. However, I was also taught that virtual functions are what allow overriding in inheritance.
So what is the difference?
If I can already redefine a base-class function in a derived class without using virtual, what exactly does virtual add, and why is it important?
I want to understand what problem virtual functions solve and when they are actually needed.
Short Answer
By the end of this page, you will understand the real purpose of virtual functions in C++: they enable runtime polymorphism. You will see the difference between simply redefining a function in a derived class and calling the correct function through a base-class pointer or reference. You will also learn when virtual is necessary, how it affects program behavior, and common mistakes beginners make.
Concept
In C++, a derived class can declare a function with the same name and signature as a base-class function. At first glance, this looks like overriding whether or not virtual is used.
The key difference is how the function call is chosen.
Without virtual
The function call is resolved based on the static type of the variable, pointer, or reference at compile time.
This is called static binding or early binding.
With virtual
The function call is resolved based on the actual object type at runtime.
This is called dynamic binding, late binding, or runtime polymorphism.
That is the real reason virtual functions exist.
Why this matters
Inheritance becomes much more useful when you can write code that works with a base type, but still gets derived-specific behavior.
For example:
- a
Shape*can point to aCircleorRectangle - an
Animal&can refer to aDogorCat - a
Logger*can hold a or
Mental Model
Think of a base-class pointer like a remote control labeled Base.
Without virtual, the remote only looks at the label on the remote, not the actual device it is connected to. If the remote says Base, it always sends the Base command.
With virtual, the remote checks the real device connected at runtime. If it is actually a Derived object, it sends the Derived version of the command.
So:
- non-virtual function = "use the function based on the variable type"
- virtual function = "use the function based on the actual object type"
Another way to think about it:
- Without
virtual, inheritance is mostly about code reuse. - With
virtual, inheritance also becomes a way to express interchangeable behavior.
Syntax and Examples
Basic syntax
class Base {
public:
virtual void show() {
// base implementation
}
};
class Derived : public Base {
public:
void show() override {
// derived implementation
}
};
Notes
virtualis written in the base class.overrideis optional but strongly recommended in the derived class.- Once a function is virtual in the base class, it stays virtual in derived classes.
Example 1: Without virtual
#include <iostream>
using namespace std;
class Animal {
public:
void makeSound() {
cout << "Animal sound\n";
}
};
: Animal {
:
{
cout << ;
}
};
{
Dog dog;
Animal* a = &dog;
dog.();
a->();
}
Step by Step Execution
Consider this example:
#include <iostream>
using namespace std;
class Base {
public:
virtual void greet() {
cout << "Hello from Base\n";
}
};
class Derived : public Base {
public:
void greet() override {
cout << "Hello from Derived\n";
}
};
int main() {
Derived d;
Base* ptr = &d;
ptr->greet();
}
Step by step
-
Derived d;- A
Derivedobject is created. - Because
Derivedinherits fromBase, it contains aBasepart too.
- A
Real World Use Cases
1. Drawing different UI elements
A graphics system may store many objects as Widget* or Shape*:
ButtonTextBoxCheckboxCircleRectangle
Each one implements its own draw() behavior. A render loop can call draw() on the base type and let virtual dispatch choose the right implementation.
2. Game development
Games often have a base class like Entity or Character with virtual functions such as:
update()render()takeDamage()
A game loop can work with Entity* while each derived class behaves differently.
3. Logging systems
A program may define a base Logger class and derived classes such as:
Real Codebase Usage
In real projects, virtual functions are usually used as part of a base interface or abstract class.
Common patterns
1. Interface-style base classes
class Logger {
public:
virtual void log(const std::string& message) = 0;
virtual ~Logger() = default;
};
This says every logger must provide log().
2. Abstract base classes
Using = 0 creates a pure virtual function, making the class abstract:
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};
You cannot create a Shape directly, but derived classes like or can implement .
Common Mistakes
1. Thinking same-name functions always give polymorphism
A derived class can hide or redefine a base function, but that alone does not create runtime polymorphism.
Broken expectation
class Base {
public:
void show() {
}
};
class Derived : public Base {
public:
void show() {
}
};
Calling show() through a Base* still uses Base::show().
Fix
Make the base function virtual if you want runtime dispatch.
2. Forgetting override
Without override, you might accidentally create a different function instead of overriding.
Broken code
class Base {
:
{
}
};
: Base {
:
{
}
};
Comparisons
| Concept | Without virtual | With virtual |
|---|---|---|
| Binding time | Compile time | Runtime |
| Based on | Static type of pointer/reference/object | Actual object type |
| Polymorphism | No runtime polymorphism | Yes |
| Base pointer to derived object | Calls base version | Calls derived version |
| Typical use | Simple inheritance, code reuse | Interfaces, extensible behavior |
Overriding vs hiding
| Situation | Result |
|---|---|
| Derived defines same function, base not virtual | Function hiding/redefinition for derived objects, not runtime polymorphism |
Cheat Sheet
Quick rules
- Use
virtualin the base class when derived classes should provide different behavior. - Virtual dispatch works through base pointers and base references.
- It does not help if you pass objects by value.
- Use
overridein derived classes. - If a class is used polymorphically, give it a virtual destructor.
Basic syntax
class Base {
public:
virtual void f();
virtual ~Base() = default;
};
class Derived : public Base {
public:
void f() override;
};
Pure virtual function
class Base {
public:
virtual void f() = 0;
};
FAQ
Why can I redefine a function without virtual?
Because C++ allows a derived class to declare a function with the same name and signature. But without virtual, calls through a base pointer or reference still use the base version.
What problem do virtual functions solve in C++?
They let base-class pointers and references call the correct derived implementation at runtime. This is runtime polymorphism.
Do I always need virtual when using inheritance?
No. Use virtual when you want different derived behavior through a base interface. If inheritance is only for code reuse, it may not be needed.
What is the difference between virtual and override?
virtual enables runtime dispatch. override tells the compiler that a derived function is intended to override a base virtual function.
Do virtual functions work with objects, pointers, and references?
They matter mainly with pointers and references to base classes. Passing or storing by value loses polymorphic behavior.
Why should a base class destructor be virtual?
So deleting a derived object through a base pointer runs the full destruction chain correctly.
What is a pure virtual function?
A pure virtual function uses = 0 and makes the class abstract. Derived classes must implement it before objects of those classes can be created.
Mini Project
Description
Build a small animal sound system that demonstrates why virtual functions are needed. You will create a base class and multiple derived classes, then call a shared function through base-class pointers. This shows the exact difference between ordinary inherited functions and virtual dispatch in a realistic, easy-to-test example.
Goal
Create a program where different animal objects respond correctly through a common base-class interface.
Requirements
- Create a base class named
Animalwith a sound-related member function. - Create at least two derived classes such as
DogandCat. - Store derived objects using base-class pointers or smart pointers.
- Call the sound function through the base type and ensure the correct derived behavior happens.
- Add a virtual destructor to the base class.
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.
C++ Lambda Expressions Explained: What They Are and When to Use Them
Learn what C++ lambda expressions are, why they exist, when to use them, and how they simplify callbacks, algorithms, and local logic.