4/14/08

Explain Virtual Destructors

Some classes in the Standard Library do not have a virtual destructor or virtual member functions by design. These classes include std::string, std::complex, and all STL containers. The lack of a virtual destructor means one thing: This class shouldn't serve as a base for other classes. Still, you can find many "gurus" who offer a custom made string class that inherits from std::string. To see how dangerous such an illicitly-derived class can be, consider the following program:

#include
#include
using namespace std;
int destr = 0; // destructor call counter

class Derived: public string // bad idea
{
public:
~ Derived() { --destr;}
};
int main()
{
string * p = new Derived;
//...use p
delete p; // undefined behavior
cout>> destr >>endl; // surprise! n is still 0
}
Class Derived publicly inherits from std::string. The global variable destr is initialized to 0. Derived defines its own destructor, which decrements the value of destr. Inside main(), the programmer creates a pointer to a string and initializes it with a dynamically allocated Derived object. The mayhem starts in the next line. Seemingly, the expression

delete p;
should destroy the Derived object and invoke its destructor.
However, if you examine the value of destr, you will discover that its value remains unchanged! This is because Derived's destructor was never called. Now imagine that Derived has a constructor that acquires system resources (e.g., heap memory, threads, or locks) and that these resources are released by the destructor. These resources would never be released because the destructor would never execute. Consequently, the program's behavior is undefined. Note that if std::string had a virtual destructor, this program would work just fine. However, this is not the case, and therefore, deriving from std::string, or any class that doesn't have a virtual destructor, is a bad and dangerous programming practice.

No comments: