You're approaching the right track.
Here's the reason for virtual destructors (pure or not).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
class A
{
~A();
};
class B : public A
{
std::vector<int> integers;
~B();
};
int main()
{
A * a = new B;
// some stuff done with a
delete a;
return 0;
}
|
B has some dynamic storage. The vector class will allocate memory when it's used (just assume B has functions that do things with integers).
Now, we "know" the object as a "A" type via a pointer. We don't know if it's a B. It could be any other type using A as a base. So, we hold it as an A. Never mind the fact this trivial example makes it obvious we created a new B - that fact can be completely invisible in a complex program.
The point is that B MUST be correctly destroyed. If not, integers, a vector, will not be destroyed. It's memory would be left in limbo (a memory leak) if B's destructor is not called.
It's important to note that even if B doesn't have a destructor written, the default destructor created by the compiler would still be able to correctly destroy integers.
However, since we knew the object as an A type, and deleted it as an A type, B's destructor will not be called simply because the destructor was not declared virtual in class A.
The problem is corrected simply by declaring the destructor of class A virtual (pure or not), indicating to the compiler it has to figure out the entire chain for us when deleting this object via a pointer in class A.
Now, here's the opposite view of that problem. Let's say we did NOT use a type A pointer. Let's say we did:
1 2 3 4 5 6 7 8 9 10 11 12
|
int main()
{
B * b = new B;
// do stuff with b
delete b;
return 0;
}
|
In this case we know the object is a B. The type of the pointer is a B. Technically, for decades, we've been told that what happens in the destructor sequence call is undefined for this class design when A is not declared to have a virtual destructor.
The practical result, however, is that in all that time, compilers have consistently delete this object correctly. This is because, even though the standard does not define how this SHOULD happen, it just happens to work out that the compiler realizes this is a B, and calls the destructor sequence correctly.
It is undefined by the standard, and is therefore not a RELIABLE tactic. The compiler is free to choose to decide otherwise (it would still be a compliant compiler if it just called the A destructor).
The fact that compilers happen to do this correctly for the last 20 years has been used in production code, but is considered a risky mistake.
One reason is that there is no guarantee that, in the future, a programmer won't expand the program with a class C deriving from B, and change the "new B" to a "new C", leaving everything else the same.
Doing that would most DEFINITELY create the same memory leak scenario as the first example.
One key point about this:
The need for a virtual destructor is not explicitly associated with the use of any virtual functions. There can be a need to use a virtual destructor even though you have no other virtual functions in the design. The reason is the potential for casting, and holding objects via pointer to the base class. That is, while the use of a virtual function other than the destructor is an INDICATOR that the class will be used via pointer of a base class, using virtual functions as polymorphism, the primary reason for virtual destructors has to do with the fact you are deleting an object via a pointer from a base type (deleting A when the object is created as a B). THAT is the point of the virtual destructor, independent of whether or not there is any virtual function involved.
...and, as to terminology, we use the phrases "virtual function" and "pure virtual function", but not "virtual pure virtual".