Virtual Functions in C++: Why Can't You Do Without Them?
\n\nImagine you're a director filming a movie about animals. You have a script: "All animals make a sound." You shoot a scene with a dog — it barks. A scene with a cat — it meows. Everything makes sense. Now imagine your assistant (the compiler) brings you a box of toy animals and says: "Shoot!" You press the button, and the toy... stays silent. Why? Because you forgot about virtual functions.
\n\nIn C++ programming, this problem occurs constantly. You write a class "Animal", inside it a method sound(). Then you create classes "Dog" and "Cat" that inherit from "Animal". And everything seems fine, until you try to work with these objects through a pointer to the base class. That's when it turns out that C++ by default calls the base class method, not the overridden one in the derived class. Virtual functions solve this problem once and for all.
In this article, we'll thoroughly break down what virtual functions are, why they are needed, how they work under the hood, and why modern C++ is unthinkable without them.
\n\nWhat is a Virtual Function in Simple Terms
\n\nA virtual function is a class method that you mark with the keyword virtual. Such a function can be overridden in derived classes, and when called through a pointer or reference to the base class, the version from the derived class will be invoked.
Sounds complicated? Let's use a real-life example. Imagine a remote control for robots. You have a base class "Robot" with a method doWork(). There is a robot vacuum and a robot courier. If you press the "Work" button, the corresponding algorithms should start: the vacuum starts cleaning, the courier starts delivering a package. Without virtual functions, you would press the button, and the robot would just stand still (the base class method would be called).
In C++, virtual functions are the foundation of polymorphism. Polymorphism allows you to uniformly work with objects of different types if they inherit from a common ancestor.
\n\nThe Problem Without Virtual Functions: A Clear Example
\n\nLet's see what happens if you don't use virtual.
#include <iostream>\n#include <vector>\n\nclass Animal {\npublic:\n void sound() {\n std::cout << "Some generic animal sound" << std::endl;\n }\n};\n\nclass Dog : public Animal {\npublic:\n void sound() {\n std::cout << "Woof! Woof!" << std::endl;\n }\n};\n\nclass Cat : public Animal {\npublic:\n void sound() {\n std::cout << "Meow!" << std::endl;\n }\n};\n\nint main() {\n std::vector<Animal*> animals;\n animals.push_back(new Dog());\n animals.push_back(new Cat());\n\n for (Animal* a : animals) {\n a->sound(); // What will be printed?\n }\n\n // Memory cleanup\n for (Animal* a : animals) delete a;\n\n return 0;\n}\n\n\nRun this code. You will see:
\nSome generic animal sound\nSome generic animal sound\n\n\nWhy? Because the compiler looks at the pointer type (Animal*), not the actual object. It calls the base class method. This is called early binding — the decision about which method to call is made at compile time.
But we want late binding — so that the decision is made at runtime, depending on the type of the object. This is exactly what virtual functions provide.
\n\nThe Solution: Adding virtual
\n\nLet's fix the example by adding the keyword virtual.
class Animal {\npublic:\n virtual void sound() { // Added virtual\n std::cout << "Some generic animal sound" <<