Department of Engineering

IT Services

C++ destructors

Basics

A constructor function is called when an object's created, and a destructor function when the object's destroyed. Sometimes the default functions suffice, but you sometimes need to write your own. Here's a simple class with a constructor and destructor that print output to show when they're called.

class Base
{
public:
  string name;
  Base(string n) {name=n; cout << "Base Constructor for " << name << endl;}
  ~Base() {cout << "Base Destructor for " <<name << endl;}
};

To see this in operation, try this -

#include <string>
#include <iostream>

using namespace std;

class Base
{
public:
  string name;
  Base(string n) {name=n; cout << "Base Constructor for " <<name << endl;}
  ~Base() {cout << "Base Destructor for " << name << endl;}
};

int main() {
  Base b("base 1");
}

Note that when the variable b goes out of scope, its destructor is called. If the constructor uses new to claim a lot of memory, it's important that the destructor's eventually called to free the memory, otherwise the process will use up resources. At the moment everything's ok - the variable b is created and destroyed.

New and Delete

Let's add a line to main

  Base *bp=new Base("base 2");

If the code's compiled and run now, you'll see that the new variable is created, but it's not destroyed when bp dies. The memory used by the pointer bp is freed, but not what bp is pointing to. To solve that add

  delete bp;

to the end of the program. Recompile and run to confirm that constructors and destructors are being called appropriately.

Inheritance

Now let's use inheritance. The following class uses Base -

class Derived: public Base
{
public:
  Derived(string n):Base(n) { cout << "Derived Constructor for " <<name << endl;}
 ~Derived() {cout << "Derived Destructor for " <<name << endl;}
};

Add this class definition to your code and try it out using the following main function

int main() {
  Derived d("derived1");
  Base *bp=new Derived("derived2");

  delete bp;
}

This time we've remembered to delete bp. Note that when an object of type Derived is created, the Base constructor then the Derived constructor is called. A similar thing should happen when the object's destroyed - the Derived destructor then the Base destructor is called. For the "derived1" object, that's the case, but there's a problem with the "derived2" variable. Even though bp points to an object of type Derived, only the destructor of the Base is called. This may lead to resources not being freed.

Virtual Functions

To fix this, change the Base destructor to

 virtual ~Base() {cout << "Base Destructor for "  << name  <<  endl;}

Rather than just trusting the type of bp to decide what delete should do (it's pointing to a Base which is why the Base constructor is called), the virtual keyword causes the code to determine what bp is really pointing to (in this case, a Derived object) and calls that object's destructor instead.

It's for this reason that there's a rule of thumb saying "make base classes' destructors virtual when they're meant to be manipulated polymorphically"

Exceptions and destructors

There's another rule of thumb saying that you should never throw exceptions from a destructor. If you don't know what exceptions are, I suggest you stop reading now.

The following code is adapted from Intel's documentation. It creates a bad class that throws from its destructor, and redefines the new and delete operators for it so that they print something when they're called.

#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>

using namespace std;

#include <cstdlib>

class Bomb { // bad class throws exception from destructor
    string name;
public:
     Bomb(string s) : name(s) {cout <<"constructor called for " << name << endl;}
    ~Bomb() {cout <<"destructor called for " << name << endl; throw "boom"; }
    // new is by default a static member. The "throw()" promises not to
    // throw exceptions from this method
    void * operator new(size_t n) throw()
    {
      cout <<"operator new called\n";
        return malloc(n);
    }
    
    //by default a static member
    void operator delete(void *p) throw()
    {
      cout << "operator delete called\n"; // never gets here
      if (p != 0) 
         free(p);
    }
};

void f() {
    Bomb Bomb1("Bomb1");
    
    Bomb *pBomb = new Bomb("Bomb2");
    try {
        delete pBomb;
    } catch (...) {
        // Gets here but leaks storage.  Print output shows that
        // operator new was called but operator delete was not.
      cout <<"caught exception from destructor\n";
    }
    
    // program dies here: can't throw two exceptions
    // at the same time, so terminates
    throw "no boom"; // program dies here
}

int main()
{
    try {
        f();
    } catch (char *message) {
      cout << "f threw " << message << endl; // never gets here
    }
}

When you run this you'll get something like

constructor called for Bomb1
operator new called
constructor called for Bomb2
destructor called for Bomb2
caught exception from destructor
terminate called after throwing an instance of 'char const*'
Aborted

Bomb1 is created using the constructor. Then Bomb2 is created and straight away deleted. The delete operation is within a try ... catch block so the exception thrown by the destructor is caught. The problem is that when an object is deleted, two things happen: first the destructor is called and second the delete operator is called. If the destructor throws an exception, delete won't be called and resources will be wasted.

There's another problem. At the end of the f function the Bomb2 exception is still active. The lifetime of Bomb1 is over so its destructor is called, causing an exception. Consequently, there's a queue of exceptions to be dealt with. C++ can't deal with that so the program terminates.

See Also