The whole point of inheritance is to share a common interface and protocol among many different implementations such that an instance of a derived class can be treated identically to any other instance from any other derived type.
In C++ inheritance also brings with it implementation details, marking (or not marking) the destructor as virtual is one such implementation detail.
Now when a function, or any of its special cases like a constructor or destructor is called, the compiler must choose which function implementation was meant. Then it must generate machine code that follows this intention.
The simplest way to work this would be to select the function at compile time and emit just enough machine code so that regardless of any values, when that piece of code executes, it always runs the code for the function. This works great except for inheritance.
If we have a base class with a function (could be any function, including the constructor or destructor) and your code calls a function on it, what does this mean?
Taking from your example, if you called
initialize_vector() the compiler has to decide if you really meant to call the implementation found in
Base, or the implementation found in
Derived. There are two ways to decide this:
- The first is to decide that because you called from a
Base type, you meant the implementation in
- The second is to decide that because the runtime type of the value stored in the
Base typed value could be
Derived that the decision as to which call to make, must be made at runtime when called (each time it is called).
The compiler at this point is confused, both options are equally valid. This is when
virtual comes into the mix. When this keyword is present the compiler picks option 2 delaying the decision between all the possible implementations till the code is running with a real value. When this keyword is absent the compiler picks option 1 because that is the otherwise normal behaviour.
The compiler might still pick option 1 in the case of a virtual function call. But only if it can prove that this is always the case.
Constructors and Destructors
So why don't we specify a virtual Constructor?
More intuitively how would the compiler pick between identical implementations of the constructor for
Derived2? This is pretty simple, it can't. There is no pre-existing value from which the compiler can learn what was really intended. There is no pre-exisiting value because that is the job of the constructor.
So why do we need to specify a virtual destructor?
More intuitively how would the compiler choose between implementations for
Derived? They are just function calls, so the function call behaviour happens.
Without a declared virtual destructor the compiler will decide to bind directly to the
Base destructor regardless of the values runtime type.
In many compilers, if the derived does not declare any data members, nor inherit from other types, the behaviour in the
~Base() will be suitable, but it is not guaranteed. It would work purely by happenstance, much like standing in front of a flamethrower that had not yet been ignited. You are fine for a while.
The only correct way to declare any base or interface type in C++ is to declare a virtual destructor, so that the correct destructor is called for any given instance of that type's type hierarchy. This allows the function with the most knowledge of the instance to clean that instance up correctly.