ACCU Home page ACCU Conference Page ACCU 2017 Conference Registration Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinWhingeing Session

Overload Journal #1 - Apr 1993 + Programming Topics   Author: Mike Toms

One of the many uses of destructors is to provide a means of freeing up all the memory used by the object when it exits scope. But this job is all too often only half done. This short whinge is limited only to the superficial understanding of the new/delete mechanism exhibited by many C++ programs I have seen. (I have been guilty of this myself when coding without my thinking cap on.)

The following section of code is the harness into which each of the examples will be placed.

  #include  <iostream.h>
  #include  <conio.h>
  #include  <alloc.h>
  #include  <math.h>

  // class defintion of Test
  class  Test
  {
  protected:
    long*  Q;
    int instance;
    static int  counter;
  public:
    Test(void);
    ~Test(void);
  };
  int Test::counter = 0;
  Test::Test(void)
  {
    instance = counter++;
    Q = new long;
    cout << "Constructing instance "
         << instance
         << " of Test"
         << endl;
  }
  Test::~Test(void)
  {
    delete Q;
    cout << "Destructing instance "
         << instance
         << " of Test"
         << endl;
  }
  void main(void)
  {
    unsigned
  #if defined( __COMPACT__ ) || defined( __LARGE__ ) \
             || defined(__HUGE__ )
    long
  #endif
    initial_memory, final_memory;
    clrscr () ;
    initial_memory = coreleft() ;
    cout << "Memory before start of test = "
         << hex
         <<  initial_memory
         << endl
         << "--------------------------------"
         << endl;
    //
    // start of test
    //
    // end of test
    //
    final_memory = coreleft();
    cout << "--------------------------------"
         << endl
         << "memory at end of test = "
         << hex
         << final_memory
         << endl;
    if (final_memory != initial_memory)
    {
      cout << "There was a "
           << (final_memory < initial_memory ? "loss" : "gain")
           << " of "
           << dec
           << labs(initial_memory - final_memory)
           << " bytes"
           <<  endl;
    }
    getch();
  }
   

I have often been asked by experienced C programmers "Why should I use new and delete? I am happy using malloc and free!". The easiest way to demonstrate the answer is to enter the following two extracts, one at a time into the harness and run them.

// Extract One
  Test* X = (Test*) malloc (sizeof(Test));
  free( X);

// Extract Two
  Test* X = new Test;
  delete X;

The output for the for these tests is as follows

Results one

  Memory before start of test = 76860
  --------------------------------
  --------------------------------
  memory at end of test = 76860

Results Two

  Memory before start of test = 76860
  --------------------------------
  Constructing instance 0 of Test
  Destructing instance 0 of Test
  --------------------------------
  memory at end of test = 76860

The obvious difference in effect is that the constructor and destructor have not been executed when malloc/free is used. Thus the intended initialisation and destruction are not performed. It is possible to mix malloc/free and new/delete as in the following extracts:

// Extract Three
  Test* X = new Test;
  free ( X);

Results Three

  Memory before start of test = 76860
  --------------------------------
  Constructing instance 0 of Test
  --------------------------------
  memory at end of test = 76840
  There was a loss of 32 bytes

// Extract Four
  Test* X = (Test*) malloc (sizeof(Test));
  delete X;

Results Four
  Hangs.....
  Well not even ALT-CTRL-DEL resets my machine......
  Under Windows a serious error occurs..............
  Don't run it with other applications up.

This obviously causes problems in one or other of the constructor/destructor functions. The moral of this is *NEVER* use malloc/free in a C++ program. When run under Windows, extract 4 can cause spectacular effects. I will probably be accused of being stupid about this, but it does represent my views.

Another good reason for new/delete, as if the first is not convincing enough, is that the overloading of the constructor (never called by malloc) is essential to being able to control copy construction and provide a plethora of parameterised constructors.

The other thing that grossly annoys me is arrays of dynamically allocated objects not being cleaned up.

// Extract Five
  Test* X = new Test[10];
  delete X;

Results Five

  Memory before start of test = 76860
  --------------------------------
  Constructing instance 0 of Test
  Constructing instance 1 of Test
  Constructing instance 2 of Test
  Constructing instance 3 of Test
  Constructing instance 4 of Test
  Constructing instance 5 of Test
  Constructing instance 6 of Test
  Constructing instance 7 of Test
  Constructing instance 8 of Test
  Constructing instance 9 of Test
  Destructing instance 0 of Test
  --------------------------------
  memory at end of test = 76770
  There was a loss of 240 bytes

Although all the memory allocated by new has been freed, only the destructor of the first object has been called. This has caused the memory allocated by the other instantiations of Test to remain, resulting in a gross memory bleed. The solution to this problem is to use the correct syntax for the deletion of arrays as in extract 6.

// Extract Six
  Test* X = new Test[10];
  delete [] X;

Results Six

  Memory before start of test = 76860
  --------------------------------
  Constructing instance 0 of Test
  Constructing instance 1 of Test
  Constructing instance 2 of Test
  Constructing instance 3 of Test
  Constructing instance 4 of Test
  Constructing instance 5 of Test
  Constructing instance 6 of Test
  Constructing instance 7 of Test
  Constructing instance 8 of Test
  Constructing instance 9 of Test
  Destructing instance 9 of Test
  Destructing instance 8 of Test
  Destructing instance 7 of Test
  Destructing instance 6 of Test
  Destructing instance 5 of Test
  Destructing instance 4 of Test
  Destructing instance 3 of Test
  Destructing instance 2 of Test
  Destructing instance 1 of Test
  Destructing instance 0 of Test
  --------------------------------
  memory at end of test = 76860

As can be seen, the destructor has now been called for each of the instantiations, and I am Happy.

End of whinge!

Overload Journal #1 - Apr 1993 + Programming Topics