EDIT: After some superb comments, I've made some corrections to my original code: (1) virtual
destructors, and (2) use of the override
and final
keywords.
Have you ever noticed C++'s inheritance system behaving in a way you didn't expect? Perhaps the base class's function kept getting called, and you didn't know how to have the derived class's function called instead. Or maybe you encountered some weird code in the class definition, things like virtual
and =0;
.
These all relate to virtual inheritance, which isn't nearly as scary as it first looks! Let's create a basic example to demonstrate what's going on with virtual
, and why it is so awesome.
Let's imagine that we have a basic class, Animal
, and that we derive a new class Dog
from it...
class Animal
{
public:
Animal(){}
void eat()
{
std::cout << "Nom nom nom" << std::endl;
}
void sit()
{
std::cout << "[stares blankly]" << std::endl;
}
void speak()
{
std::cout << "[undefined sound]" << std::endl;
}
~Animal(){}
};
class Dog : public Animal
{
public:
Dog(){}
void sit()
{
std::cout << "[sits]" << std::endl;
}
void speak()
{
std::cout << "woof" << std::endl;
}
~Dog(){}
};
class Cat : public Animal
{
public:
Cat(){}
virtual void sit()
{
std::cout << "[meows disdainfully and walks away]" << std::endl;
}
virtual void speak()
{
std::cout << "mew" << std::endl;
}
~Cat(){}
};
int main()
{
Dog* dog = new Dog();
Cat* cat = new Cat();
dog->eat();
dog->sit();
dog->speak();
cat->eat();
cat->sit();
cat->speak();
}
This class would define a basic animal, so we can reuse some functions, such as eat()
, which are common to all animals.
Then we override sit()
and talk()
to be specific to Dog
. It all works pretty well!
Nom nom nom
[sits]
woof
Nom nom nom
[meows disdainfully and walks away]
mew
Great! That does exactly what we want. However, one of the advantages of inheritance is that we can write functions like this...
void makeAct(Animal* critter)
{
critter->eat();
critter ->sit();
critter->speak();
}
So, instead of writing two (or more) functions that do the same thing for each type of animal, we just accept the base class (or a pointer/reference to it) as the argument type. Then, we can do this...
int main()
{
Dog* dog = new Dog();
Cat* cat = new Cat();
makeAct(dog);
makeAct(cat);
}
Here is where things get weird! When we run this, we get...
Nom nom nom
[stares blankly]
[undefined sound]
Nom nom nom
[stares blankly]
[undefined sound]
Oy oy! That's not what we're looking for, is it? Where's our woofs and mews? Why isn't the dog sitting and the cat aloofing? They're all acting like the boring base Animal
class!
This is where virtual
functions come in handy. By default, the compiler looks to the base class for the function definition. virtual
tells the computer to look at the derived class for the function definition instead.
Going hand-in-hand with this are abstract classes, a special type of virtual
function that must be defined in the derived class. To break this down:
virtual
functions may be overridden by the derived class; the derived's version of the function will be used, even if the base class is used as the data type.abstract or pure virtual functions MUST be overridden by the derived class - they don't even have a definition in the base class.
eat()
is pretty much the same among all animals, so that's fine as it is. However, we know that we should optionally override sit()
; untrained animals would stare blankly, but some would respond in specific ways.
Looking at our class design, we also realize that speak()
is a rather stupid function to define in Animal
...printing "[undefined sound]" just looks dumb. Any animal we define should have a sound, or else explicitly say something like "[no sound]". So, we'll make this pure virtual.
DESIGN PRINCIPLE: Explicit is better than implicit. In other words, every situation should have some specifically designated action (or failure) in the code. This is also why we defined explicitly empty constructors and destructors in all our classes, instead of having the compiler implicitly define them.
So, let's rewrite so that sit()
is virtual
, and speak()
is pure virtual.
Our base class should also have a virtual destructor.
class Animal
{
public:
Animal(){}
void eat()
{
std::cout << "Nom nom nom" << std::endl;
}
virtual void sit()
{
std::cout << "[stares blankly]" << std::endl;
}
virtual void speak() = 0; // the `= 0;` literally means "not defined here"
virtual ~Animal(){}
};
Meanwhile, in the derived classes, we add the [override](http://en.cppreference.com/w/cpp/language/override)
keyword (C++11 and later) to each of the functions we are overriding. This gives us compiler errors if we are trying to override a non-virtual function.
class Dog : public Animal
{
public:
Dog(){}
void sit() override
{
std::cout << "[sits]" << std::endl;
}
void speak() override
{
std::cout << "woof" << std::endl;
}
~Dog() override {}
};
We can also use the [final](http://en.cppreference.com/w/cpp/language/final)
keyword (C++11 and later) instead of override
if we don't plan to override that function later.
Dogs might make different sounds and behave differently, so we might derive from Dog
and make classes for specific breeds. Cats, on the other hand, basically all say "mew", and none of 'em will sit for you, so there's no need to override those further! Thus, for the Cat
class, instead of using override
, let's just use final
, to prevent further overriding!
class Cat : public Animal
{
public:
Cat(){}
void sit() final
{
std::cout << "[meows disdainfully and walks away]" << std::endl;
}
void speak() final
{
std::cout << "mew" << std::endl;
}
~Cat() override {}
};
Later, if we try to override the speak()
or sit()
function in a class derived from Cat
, we'll get a compiler error.
Now let's rerun that code from earlier. Here it is again, in case you forgot. We haven't changed anything here!
void makeAct(Animal* critter)
{
critter->eat();
critter ->sit();
critter->speak();
}
int main()
{
Dog* dog = new Dog();
Cat* cat = new Cat();
makeAct(dog);
makeAct(cat);
}
When we run it, we see...
Nom nom nom
[sits]
woof
Nom nom nom
[meows disdainfully and walks away]
mew
Right on! Now everything works as we expect.
Now, one word of caution: if you define a function as pure virtual or abstract (i.e. virtual thefunc() = 0;
, you MUST define it in each derived class. If you don't, you'll get a compiler error. Arguably, that's one of the benefits of pure virtual functions...the compiler is able to step in and keep you from doing stupid things.
That's it! virtual
simply allows you to control where functions are called in a class inheritance. Not so scary now, is it?