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 aConsoleLoggerorFileLogger
Without virtual, calling a function through the base type uses the base version, even if the object is actually a derived object.
With virtual, C++ chooses the derived version automatically.
Important distinction
Redefining a function in a derived class is not the full story. The question is:
What happens when you access the object through a base-class interface?
That is exactly what virtual controls.
Example
#include <iostream>
using namespace std;
class Base {
public:
void speak() {
cout << "Base speaks\n";
}
};
class Derived : public Base {
public:
void speak() {
cout << "Derived speaks\n";
}
};
int main() {
Derived d;
Base* ptr = &d;
d.speak(); // Derived speaks
ptr->speak(); // Base speaks
}
Even though d is a Derived, ptr is a Base*, so without virtual, the compiler chooses Base::speak().
Now with virtual:
#include <iostream>
using namespace std;
class Base {
public:
virtual void speak() {
cout << "Base speaks\n";
}
};
class Derived : public Base {
public:
void speak() override {
cout << "Derived speaks\n";
}
};
int main() {
Derived d;
Base* ptr = &d;
d.speak(); // Derived speaks
ptr->speak(); // Derived speaks
}
Now the function call depends on the real object type at runtime.
Why it matters in real programming
Virtual functions let you:
- write reusable code against interfaces or base classes
- swap implementations without changing caller code
- build plugin-style systems
- handle many object types through one common API
This is a major foundation of object-oriented design in C++.
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";
}
};
class Dog : public Animal {
public:
void makeSound() {
cout << "Bark\n";
}
};
int main() {
Dog dog;
Animal* a = &dog;
dog.makeSound(); // Bark
a->makeSound(); // Animal sound
}
The object is a Dog, but the pointer type is Animal*, so the base function is called.
Example 2: With virtual
#include <iostream>
using namespace std;
class Animal {
public:
virtual void makeSound() {
cout << "Animal sound\n";
}
};
class Dog : public Animal {
public:
void makeSound() override {
cout << "Bark\n";
}
};
int main() {
Dog dog;
Animal* a = &dog;
dog.makeSound(); // Bark
a->makeSound(); // Bark
}
Now C++ uses dynamic dispatch and calls Dog::makeSound().
Example 3: Using references
Virtual dispatch also works with references:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "Drawing shape\n";
}
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing circle\n";
}
};
void render(Shape& shape) {
shape.draw();
}
int main() {
Circle c;
render(c); // Drawing circle
}
This is one of the most common uses of virtual functions: a general function works with a base type, while each derived type provides its own behavior.
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
-
Base* ptr = &d;- A base-class pointer is created.
- It points to the
Derivedobject. - The pointer type is
Base*, but the real object type isDerived.
-
ptr->greet();- The compiler sees that
greet()isvirtual. - Instead of forcing
Base::greet(), the program checks the actual object at runtime. - The actual object is
Derived. - So
Derived::greet()is called.
- The compiler sees that
-
Output:
Hello from Derived
What if virtual were removed?
Then step 3 changes:
- The compiler binds the call using the pointer type
Base* - It calls
Base::greet() - Output becomes:
Hello from Base
That single difference is the whole point of virtual functions.
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:
ConsoleLoggerFileLoggerNetworkLogger
Application code can call log() through a Logger* without caring which concrete logger is used.
4. File and stream abstractions
Code that reads from a base stream interface can work with:
- files
- memory buffers
- network sockets
The same function calls can behave differently depending on the real object.
5. Plugin architectures
A system can load different implementations behind a shared base interface. Virtual functions let the main program call common operations without knowing the exact plugin type.
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 Circle or Rectangle can implement area().
3. Polymorphic containers
Developers often store different derived objects together:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Animal {
public:
virtual void sound() const = 0;
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void sound() const override {
cout << "Bark\n";
}
};
class Cat : public Animal {
public:
void sound() const override {
cout << "Meow\n";
}
};
int main() {
vector<unique_ptr<Animal>> animals;
animals.push_back(make_unique<Dog>());
animals.push_back(make_unique<Cat>());
for (const auto& animal : animals) {
animal->sound();
}
}
This pattern is common in large C++ codebases.
4. Virtual destructors
If a class is meant to be used polymorphically, its destructor should usually be virtual:
class Base {
public:
virtual ~Base() = default;
};
This ensures correct cleanup when deleting through a base pointer.
5. override for safety
Real codebases often require override because it helps catch mistakes like wrong parameter types or missing const.
class Base {
public:
virtual void print() const;
};
class Derived : public Base {
public:
void print() const override;
};
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 {
public:
virtual void print() const {
}
};
class Derived : public Base {
public:
void print() {
}
};
This does not override because the const is missing.
Fix
class Derived : public Base {
public:
void print() const override {
}
};
3. Forgetting a virtual destructor
If you delete a derived object through a base pointer and the base destructor is not virtual, behavior is incorrect.
Risky code
class Base {
public:
~Base() {}
};
class Derived : public Base {
public:
~Derived() {}
};
int main() {
Base* ptr = new Derived();
delete ptr;
}
Safer version
class Base {
public:
virtual ~Base() = default;
};
4. Passing objects by value
Passing by value can slice the derived part away.
Broken code
void run(Base obj) {
obj.greet();
}
If you pass a Derived, only the Base portion is copied.
Fix
Use a reference or pointer:
void run(Base& obj) {
obj.greet();
}
5. Using virtual when polymorphism is not needed
Not every inherited function needs to be virtual. Use it when you need derived-specific behavior through a base interface.
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 |
| Base function is virtual and derived matches signature | True runtime overriding |
Pointer/reference vs object
| Access style | Virtual dispatch works? |
|---|---|
| Base pointer | Yes |
| Base reference | Yes |
| Base object by value | No |
virtual vs pure virtual
| Keyword style | Meaning |
|---|---|
virtual void f(); | Base provides a virtual function that derived classes may override |
virtual void f() = 0; | Pure virtual function; derived classes must implement it to be concrete |
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;
};
Remember
- Same function name in derived class does not automatically mean runtime polymorphism.
virtualchanges how calls are resolved.overridehelps the compiler catch mistakes.
Common edge cases
- Missing
constmeans no override. - Different parameter list means no override.
- Passing by value causes slicing.
- Deleting through a base pointer needs a virtual destructor.
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
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
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++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.