Inheritance
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.
Function Binding
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 Base
.
- The second is to decide that because the runtime type of the value stored in the
Base
typed value could be Base
, or 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 Derived
and 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 Base
and 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.
~derived()
that delegates to vec's destructor. Alternatively, you are assuming thatunique_ptr<base> pt
would know the derived destructor. Without a virtual method, this cannot be the case. While a unique_ptr may be given a deletion function that is a template parameter without any runtime representation, and that feature is of no use for this code.