Destructors

Destructors
- Special type of class member function

- Automatically called when object is out of function scope or explicitly deleted

- Same name as the class with addition of ~ (tilde) symbol before name

- No return type and no arguments allowed

- Consider the class definition for an image file (storing a rectangular block of colors)


#include <iostream>

using namespace std;

 

class Image

{

private:

  unsigned char *colors;

  string name;

  int width;

  int height;

  int size;

 

public:

  Image( string s="", int x=0, int y=0 );

  ~Image();

 

  void Print();

};

 

// Constructor

Image::Image( string s, int w, int h )

{

  name = s;

  width = w;

  height = h;

  size = width * height;

 

  // Allocate memory for colors

  // (using conditional expression, see below)

  colors = size ? new unsigned char[ size ] : NULL;  

 

  // Initialize colors

  for( int i=0; i < size; ++i )

    colors[i] = 0;

}

 

// Destructor

Image::~Image()

{

  cout << "Image destructor called for " << name << endl;

 

  // Delete memory for colors

  if( colors )

    delete[] colors;

}

 

// Print method

void Image::Print()

{

  cout << "Image:" << endl;

  cout << "  name: " << name << endl;

  cout << "  width, height: " << width << “, “ << height << endl;

}

 

- Destructors provide an ideal place to perform any final cleanup tasks associated with objects

- Such tasks could include freeing memory or closing files

- Destructors can be used to “undo” whatever was “done” in the constructor

- In the class above, for example, dynamic memory allocated in the constructor is deleted in the destructor

- Destructors are called when objects are out of function scope (when a function has completed)

- For example, when the main program (which is a function) finishes, the destructor for the small object is called

main()

{

  Image small( "small", 10, 10 );

}

 

Output

Image destructor called for small


- In the next example below the small object is local to the CreateImage() function

- When this function ends, the small object is no longer used (out of function scope) and its destructor is called

- When the main function ends, the medium object is out of function scope and it destructor is called

 

void CreateImage();

 

main()

{

  CreateImage();

  Image medium( "medium", 50, 50 );

}

 

void CreateImage()

{

  Image small( "small", 10, 10 );

}

 

Output

Image destructor called for small

Image destructor called for medium

 

 

 

Destructors and Dynamic Memory
- Objects allocated dynamically with new must be freed using delete when they are no longer needed in a program

- For dynamically allocated objects, the destructors are called if and only if they are freed explicitly with delete

- Consider the example below in which only the destructor for the object explicitly deleted (small) is called

 

main()

{

  Image *small  = new Image( "small", 10, 10 );

  Image *medium = new Image( "medium", 50, 50 );

 

  delete small;

}

 

Output

Image destructor called for small

 

- The destructor for the medium object is never called

- As a result, the memory (pointed to by colors) allocated in its constructor is never freed!

- This example serves to illustrate a common mistake made with dynamically allocated objects

- When objects are not explicitly deleted, they remain in heap memory for the duration of the program

- Many programs suffering from memory overload problems are often caused by forgetting this concept

- Consider the program below that dynamically creates an object every time through the loop

 

main()

{

  Image *img;

  for( int i = 0; i < 20; ++i )

    {

      // Allocate image on the heap, assign address to pointer

      img = new Image( "sample", 20, 20 );

 

      // Do something with the image...

 

    }

}

 

- Each time through the loop an Image object is created on the heap and its address stored in the pointer img

- In addition, each new object allocates memory on the heap in its constructor (stored by colors pointer)

- The problem is that every time an object is created, it overwrites the pointer storing the previous object

- As a result, we loose the address to the dynamic memory that is still allocated on the heap

- Without this pointer, we can no longer free this memory that will remain for the duration of the program

- If this process continued repeatedly, we could run out of heap memory and the program would fail

- Imagine what would happen in a real-time system program (example below) where this occurred in a continual loop!

- Such a program would crash or be killed by the operating system after heap memory has been exceeded

 

main()

{

  Image *img;

  while(1)

    {

      // Allocate image on the heap, assign address to pointer

      img = new Image( "sample", 2000, 2000 );

 

      // Do something with the image...

 

    }

}

 

- Instead, we want the destructor for each new Image object called before a new one overwrites the pointer

- As we’ve seen, the destructor will be called for dynamic objects if and only if it is explicitly freed with delete

- The destructor called by delete (corrected example below), will free heap memory before a new one is created

 

main()

{

  Image *img;

  while(1)

    {

      // Allocate image on the heap, assign address to pointer

      img = new Image( "sample", 2000, 2000 );

 

      // Do something with the image...

 

      // Delete the image on the heap

      delete img;

    }

}

 

 

 

Conditional Expression

The line used in the Image constructor above

 

colors = size ? new unsigned char[ size ] : NULL;

 

is known as a conditional expression which provides a compact way to code a simple if-else expression. Conditional expressions simplify the if-else syntax

 

            if( expression is TRUE )

      {

            evaluate

      }

      else

      {

            // expression is FALSE

            evaluate

      }

 

into the following syntax:

 

            ( expression ) ? (evaluate if TRUE) : (evaluate if FALSE);

 

Thus, the equivalent if-else form of our example above would be:

 

if( size )

{

      colors = new unsigned char[ size ];

}

else

{

      colors = NULL;

}