By default, when the memory for an object is being reclaimed, either because a variable goes out of scope or because an explicit call to delete on a pointer was made, the memory for the member variables is collected.
There may, however, be other operations which must be performed before the memory for an object is reclaimed. This may be done inside a destructor.
The destructor for a class ClassName has the signature ~ClassName();. It cannot take any parameters and it has no return value.
We will give two examples:
This class Counter tracks the number of instances, so each time a constructor is called, the static variable count is incremented, however, each time the destructor is called, the static variable count must be decremented.
Aside: note that static class functions are called using the class name, in this case, Counter::getCount().
#include <iostream> using namespace std; class Counter { private: static int count; public: Counter(); Counter( const Counter & ); ~Counter(); static int getCount(); }; int Counter::count = 0; Counter::Counter() { ++count; } Counter::Counter( const Counter & ) { ++count; } Counter::~Counter() { --count; } int Counter::getCount() { return count; } int main() { cout << "0. There are " << Counter::getCount() << " counters" << endl; Counter a; Counter b; cout << "1. There are " << Counter::getCount() << " counters" << endl; { Counter c; cout << "2. There are " << Counter::getCount() << " counters" << endl; } cout << "3. There are " << Counter::getCount() << " counters" << endl; Counter * ptr; // declaring a pointer does not create a new object cout << "4. There are " << Counter::getCount() << " counters" << endl; ptr = new Counter; cout << "5. There are " << Counter::getCount() << " counters" << endl; delete ptr; cout << "6. There are " << Counter::getCount() << " counters" << endl; return 0; }
Because the variables a and b only go out of scope after the function int main() returns, we will never return to zero counters.
#include <iostream> using namespace std; // declare the class and the << operator function class Array; ostream & operator << ( ostream &, const Array & ); class Array { private: int * array; const static int N = 10; public: Array( int ); ~Array(); friend ostream & operator << ( ostream &, const Array & ); }; Array::Array( int n) { array = new int[N]; for ( int i = 0; i < N; ++i ) { array[i] = i*n; } } Array::~Array() { cout << "deallocating memory from array with multiplier..." << array[1] << endl; delete [] array; } // print the entries of the array, e.g., 0-2-4-6-8-10-12-14-16-18 ostream & operator << ( ostream & out, const Array & rhs ) { out << rhs.array[0]; for ( int i = 1; i < Array::N; ++i ) { out << " - " << rhs.array[i]; } return out; } int main() { Array a( 5 ); cout << "The variable a is " << a << endl; Array * ptr; ptr = new Array( 7 ); cout << "The pointer is pointing to " << *ptr << endl; cout << "Calling delete on the pointer" << endl; delete ptr; cout << "Returning from int main()" << endl; return 0; }
In each case, the destructor is called when the memory for the object is collected, either through an explicit call to delete [] or because the variable went out of scope.
Consider the following program where each box stores 1 KiB of memory for an array of integers. Each time the constructor and destructor are called, a message is printed indicating how much memory is being allocated or deallocated. When a single box is allocated (new Box()), the constructor is called once, while when an array of boxes is allocated (new Box[2]), the constructor is called twice, once for each entry of the array. Using delete[] calls the destructor twice, while using delete calls the destructor only once with undefined results.
#include <iostream> using namespace std; // a demonstration class which allocates an array of 256 integers class Box { private: int * array; public: Box() { cout << "Allocating 1 KiB" << endl; array = new int[256]; } ~Box() { cout << "Deallocating 1 KiB" << endl; delete [] array; } }; int main() { cout << "Allocating memory for a single box" << endl; Box *box = new Box(); cout << "Deallocating memory for a single box using delete" << endl; delete box; cout << endl; cout << "Allocating memory for an array of two boxes" << endl; box = new Box[2]; cout << "Deallocating memory for an array of 2 boxes using delete[] (good)" << endl; delete [] box; cout << endl; cout << "Allocating memory for an array of two boxes" << endl; box = new Box[2]; cout << "Deallocating memory for an array of 2 boxes using delete (bad)" << endl; delete box; cout << endl; return 0; }
When this code is run on ecelinux, the result is that it silently fails, resulting in a memory leak:
{ecelinux:1} g++ main.cpp {ecelinux:2} ./a.out Allocating memory for a single box Allocating 1 KiB Deallocating memory for a single box using delete Deallocating 1 KiB Allocating memory for an array of two boxes Allocating 1 KiB Allocating 1 KiB Deallocating memory for an array of two boxes using delete [] (correct) Deallocating 1 KiB Deallocating 1 KiB Allocating memory for an array of two boxes Allocating 1 KiB Allocating 1 KiB Deallocating memory for an array of two boxes using delete (wrong!) Deallocating 1 KiB {ecelinux:3}
Notice that 1 KiB is still left allocated.
When this code is run on eceweb, the result is more traumatic:
[ece250@eceweb ~]$ g++ main.cpp [ece250@eceweb ~]$ ./a.out Allocating memory for a single box Allocating 1 KiB Deallocating memory for a single box using delete Deallocating 1 KiB Allocating memory for an array of two boxes Allocating 1 KiB Allocating 1 KiB Deallocating memory for an array of two boxes using delete [] (correct) Deallocating 1 KiB Deallocating 1 KiB Allocating memory for an array of two boxes Allocating 1 KiB Allocating 1 KiB Deallocating memory for an array of two boxes using delete (wrong!) Deallocating 1 KiB *** glibc detected *** ./a.out: munmap_chunk(): invalid pointer: 0x0958e00c *** ======= Backtrace: ========= /lib/libc.so.6(__libc_free+0x179)[0x8ef070] /usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x2ea801] ./a.out[0x8048b5e] /lib/libc.so.6(__libc_start_main+0xdc)[0x89d4e4] ./a.out(__gxx_personality_v0+0x65)[0x8048721] ======= Memory map: ======== 00238000-00317000 r-xp 00000000 03:01 1327722 /usr/lib/libstdc++.so.6.0.8 00317000-0031b000 r-xp 000de000 03:01 1327722 /usr/lib/libstdc++.so.6.0.8 0031b000-0031c000 rwxp 000e2000 03:01 1327722 /usr/lib/libstdc++.so.6.0.8 0031c000-00322000 rwxp 0031c000 00:00 0 00759000-0075a000 r-xp 00759000 00:00 0 [vdso] 0086b000-00884000 r-xp 00000000 03:01 1489369 /lib/ld-2.4.so 00884000-00885000 r-xp 00018000 03:01 1489369 /lib/ld-2.4.so 00885000-00886000 rwxp 00019000 03:01 1489369 /lib/ld-2.4.so 00888000-009b5000 r-xp 00000000 03:01 1489370 /lib/libc-2.4.so 009b5000-009b7000 r-xp 0012d000 03:01 1489370 /lib/libc-2.4.so 009b7000-009b8000 rwxp 0012f000 03:01 1489370 /lib/libc-2.4.so 009b8000-009bb000 rwxp 009b8000 00:00 0 009c3000-009e6000 r-xp 00000000 03:01 1489673 /lib/libm-2.4.so 009e6000-009e7000 r-xp 00022000 03:01 1489673 /lib/libm-2.4.so 009e7000-009e8000 rwxp 00023000 03:01 1489673 /lib/libm-2.4.so 00c5f000-00c6a000 r-xp 00000000 03:01 1489674 /lib/libgcc_s-4.1.1-20070108.so.1 00c6a000-00c6b000 rwxp 0000a000 03:01 1489674 /lib/libgcc_s-4.1.1-20070108.so.1 08048000-08049000 r-xp 00000000 16:00 6227594 /home/ece250/public_html/intro/2n/a.out 08049000-0804a000 rw-p 00001000 16:00 6227594 /home/ece250/public_html/intro/2n/a.out 0958e000-095af000 rw-p 0958e000 00:00 0 b7fab000-b7fad000 rw-p b7fab000 00:00 0 b7fbb000-b7fbd000 rw-p b7fbb000 00:00 0 bfc77000-bfc8d000 rw-p bfc77000 00:00 0 [stack] Abort [ece250@eceweb ~]$