Destructors

So far we have talked about

  • using objects

  • creating objects

But we also need to know how to:

  • dispose of objects properly

Destructors are used to finalize an object.

The main purpose of a destructor is to clean up after the object, in most cases this means deleting memory.

To find out where to delete, we need to find out who is "responsible" for a certain object. In most cases this will be the creator, but it doesn't have to be.

Assume the following classes:

Figure 12.4. A Person with a Location
A Person with a Location

In this case we can clearly assume that objects of class "Person" are responsible for their pos object (of class Location).

So whenever a Person is deleted we want their Location to be deleted as well.

Defining a destructor:

A destructor is defines like a method that has no return type (just like the constructor), the name tilde-classname (e.g. ~Person) and no parameters. There are no parameterized destructors.

Example:

class Person
{
...
public:
...
  ~Person();
...
}

Note on the location of the definition:

  • Some people define the destructor after all constructors before other methods.

  • Some people define the destructor after the very last method.

You may do either or, put please again: Do not mix within one project!

Practice!

Figure 12.5. Car and Wheels
Car and Wheels

Example solution

class Car
{
private:
  Wheel *wheels[4];
public:
  Car();
  ~Car();
};

// ...

Car::Car() 
{
  for (int i=0;i<4;i++) 
    wheels[i] = new Wheel();
}

Implementation for destructors:

Just like any other method. Example:

Person::~Person()
{
  delete pos;
}

A destructor should

  • Delete all objects this object was responsible for

  • Clean up every thing else needed. If the object resembles hardware is should close the connection. (Example on fstreams: deleting an fstream closes the file)

Practice: Implement destructor for car.

Car::~Car()
{
  for (int i=0;i<4;i++) 
    delete wheels[i];
}

How to call destructors:

Destructors are automatically called whenever an object is deleted.

  • For dynamic object references that means delete ...;

  • For static object references that means the variable runs out of scope.

Example:

Person *p = new Person("Waldo"); 
// ...
delete p;   // <-- calls the destructor
{
  Person q("Waldo");
  // ...
}  // <-- call of the destructor

Note for eclipse:

When you create classes in eclipse via the New/Class function it creates a default constructor and a destructor for you. It also follows all naming conventions and provides guards for multiple inclusions.

The destructors in eclipse have the "virtual" keyword. For now you may ignore it. It is important when we talk about inheritance.

Implicit destructors:

If you do not provide a destructor C++ will provide one for you. Unfortunately this one won't do much (unless you're using inheritance).

Here is a complete example:

// Location.h
#ifndef LOCATION_H_
#define LOCATION_H_

class Location
{
private:
  int x,y;
public:
  Location();
  Location(int x,int y) { this->x = x; this->y = y; }
  ~Location();
  int getX() { return x; }
  int getY() { return y; }
};

#endif /*LOCATION_H_*/
// Person.h
#ifndef PERSON_H_
#define PERSON_H_

#include <string>
using namespace std;

class Location;

class Person
{
private:
  string name;
  Location *loc;
public:
  Person(string name);
  Person(string name, Location *l);
  ~Person();
  void cheat();
};

#endif /*PERSON_H_*/
// Location.cpp
#include <cstdlib>
#include "Location.h"

Location::Location()
{
  // Randomizer is initialized in main.cpp
  x = rand()%100;  
  y = rand()%100;
}

Location::~Location()
{
  // Nothing to do
}
// Person.cpp
#include <iostream>
#include <string>
#include "Person.h"
#include "Location.h"

Person::Person(string name)
{
  this->name = name;
  // No location given? Ok, create a new one
  // Our destructor will make sure it gets deleted.
  this->loc = new Location();
}

Person::Person(string name, Location *l)
{
  this->name = name;
  // Location given? Use it!
  // Please note: In this case we decided that Person
  // is responsible for the location. So it must NOT
  // be deleted on the caller, but here!
  this->loc = l;
}


Person::~Person()
{
  // We're responsible for loc, so let's delete it!
  delete loc;
}

void Person::cheat()
{
  cout << name << " is at " 
       << loc->getX() << ", " << loc->getY() 
       << endl;
}
// main.cpp
#include <cstdlib>
#include <ctime>
#include <iostream>
#include "person.h"
#include "location.h"

int main()
{
  // Initialize randomizer
  srand(time(0));

  // We don't know anythign about location
  Person *w = new Person("Waldo");
  w->cheat();
  delete w;

  // Creating a location and immediately passing the
  // responsibility to the Person object
  Person *m = new Person("Max",new Location(12,34));
  m->cheat();
  delete m;

  // Creating a Location
  Location *fiftyfifty = new Location(50,50);
  // Passing it (and the responsibility for it) to 
  // Person
  Person *md = new Person("Middleman",fiftyfifty);
  md->cheat();
  // This also deletes the location
  delete md;

  // Typical mistakes! Do not do any of those!

  // the object reference by fiftyfifty just got deleted!
  cout << fiftyfifty->getX() << endl;

  Location *lc = new Location(11,22);
  Person *p1 = new Person("Person1",lc);
  Person *p2 = new Person("Person2",lc);
  // Now both p1 and p2 are responsible for lc. So both
  // will try to delete it! Bad idea....
  
  // deletes p1 and lc
  delete p1;
  
  // will crash
  p2->cheat();

  return 0;
}