Section 3 - Virtual Destructors
6A.3.1 CString Stack
Let's inherit another type of StackNode, CStringNode, that will hold a CString. We'll also inherit a CStringStack from Stack. Here are the simple prototypes of the two CString classes. Keep in mind that none of the needed protection was done here because I wanted to keep this example short. The important fact is that CStrings require allocation and deletion in the constructor and destructor, which is a fairly universal concept in C++. This is something our FloatNode and FloatStack didn't require.
Here is the code:
#include <iostream> #include <string> using namespace std; #include "Stack.h" // CStringNode and CStringStack prototypes class CStringNode : public StackNode { public: char *data; CStringNode(char *x); ~CStringNode(); void show(); }; class CStringStack : public Stack { public: ~CStringStack(); void push(char *f); bool pop(char *f); }; // main method -------------------------------------------- int main() { CStringStack sstk; sstk.push("hi"); sstk.push("mom"); sstk.push("hello"); sstk.push("dad"); sstk.showStack(); cout << endl; return 0; } // CStringNode method definitions -------------------------- CStringNode::CStringNode(char *s) { // dangerous without error checking but keep it short for demo data = new char[ strlen(s) + 1]; strcpy(data, s); } CStringNode::~CStringNode() { delete[] data; } void CStringNode::show() { cout << "[" << data << "] "; } // CStringStack method definitions ------------------------------------ void CStringStack::push(char *s) { // create a new StringNode CStringNode *sp = new CStringNode(s); // push the StackNode onto the stack (base class call) Stack::push(sp); } bool CStringStack::pop(char *s) { strcpy(s, ""); // just in case of trouble // pop a node CStringNode *sp = (CStringNode *)Stack::pop(); if (sp == NULL) return false; // dangerous without length protection, but we keep it simple strcpy(s, sp->data); delete sp; // delete the node (we're done with it) } CStringStack::~CStringStack() { // because we are creating our own nodes internally, we // do have to destroy them here. StackNode *np; while (np = Stack::pop()) delete np; }
Here is the expected output:
6A.3.2 A Problem Averted
Look at the CStringStack destructor, ~CStringStack():
CStringStack::~CStringStack() { // because we are creating our own nodes internally, we // do have to destroy them here. StackNode *np; while ( np = Stack::pop() ) delete np; }
In a manner similar to the showStack() method, we are marching through the stack with a StackNode pointer, np. And we are deleting the heap pointed to by np each pass of the loop. Since np is a StackNode pointer, the destructor of the base class would have been called had we not declared the base class destructor virtual. Moreover, the base destructor does nothing (since a generic StackNode has no heap to delete). This would have left the CStrings, i.e., the char arrays, that were allocated on the heap, locked up in limbo - they would never be deleted. In the FloatNode example, this was not an important issue, but if you remember, I mentioned that you should declare a base class destructor virtual and I would eventually explain why it was important. Well, now I'm doing the explaining!. In the present case, it would be a disaster - memory leak - if we had not provided a virtual destructor.
Fortunately, we declared the StackNode destructor to be virtual:
// Class StackNode ----------------------------------
class StackNode
{
public:
StackNode *next;
StackNode();
virtual ~StackNode();
virtual void show();
};
Once you make any one of your class methods virtual in a base class, you should also declare the destructor as virtual, because you never know if any derived class might add heap-style members to it. This assures that the derived destructor is called prior to the base class destructor.
Incidentally, you do not have to explicitly invoke the base class destructor by function chaining from the derived class constructor as you did with constructors. C++ knows to always call the base class destructor no matter what.