Overloading new & delete for arrays
If you overload operator new and
delete for a class, those operators are called whenever you create an
object of that class. However, if you create an array of those class
objects, the global operator new( ) is called to allocate
enough storage for the array all at once, and the global operator
delete( ) is called to release that storage. You can control the
allocation of arrays of objects by overloading the special array versions of
operator new[ ] and operator delete[ ] for the class. Here’s
an example that shows when the two different versions are
called:
//: C13:ArrayOperatorNew.cpp
// Operator new for arrays
#include <new> // Size_t definition
#include <fstream>
using namespace std;
ofstream trace("ArrayOperatorNew.out");
class Widget {
enum { sz = 10 };
int i[sz];
public:
Widget() { trace << "*"; }
~Widget() { trace << "~"; }
void* operator new(size_t sz) {
trace << "Widget::new: "
<< sz << " bytes" << endl;
return ::new char[sz];
}
void operator delete(void* p) {
trace << "Widget::delete" << endl;
::delete []p;
}
void* operator new[](size_t sz) {
trace << "Widget::new[]: "
<< sz << " bytes" << endl;
return ::new char[sz];
}
void operator delete[](void* p) {
trace << "Widget::delete[]" << endl;
::delete []p;
}
};
int main() {
trace << "new Widget" << endl;
Widget* w = new Widget;
trace << "\ndelete Widget" << endl;
delete w;
trace << "\nnew Widget[25]" << endl;
Widget* wa = new Widget[25];
trace << "\ndelete []Widget" << endl;
delete []wa;
} ///:~
Here, the global versions of new
and delete are called so the effect is the same as having no overloaded
versions of new and delete except that trace information is added.
Of course, you can use any memory allocation scheme you want in the overloaded
new and delete.
You can see that the syntax of array
new and delete is the same as for the individual object versions
except for the addition of the brackets. In both cases you’re handed the
size of the memory you must allocate. The size handed to the array version will
be the size of the entire array. It’s worth keeping in mind that the
only thing the overloaded operator new( ) is
required to do is hand back a pointer to a large enough memory block. Although
you may perform initialization on that memory, normally that’s the job of
the constructor that will automatically be called for your memory by the
compiler.
The constructor and destructor simply
print out characters so you can see when they’ve been called. Here’s
what the trace file looks like for one compiler:
new Widget
Widget::new: 40 bytes
*
delete Widget
~Widget::delete
new Widget[25]
Widget::new[]: 1004 bytes
*************************
delete []Widget
~~~~~~~~~~~~~~~~~~~~~~~~~Widget::delete[]
Creating an individual object requires 40
bytes, as you might expect. (This machine uses four bytes for an int.)
The operator new( ) is called, then the constructor (indicated by
the *). In a complementary fashion, calling delete causes the
destructor to be called, then the operator delete(
).
When an array of Widget objects is
created, the array version of operator new( ) is used, as
promised. But notice that the size requested is four more bytes than expected.
This extra four bytes is where the system keeps information about the array, in
particular, the number of objects in the array. That way, when you
say
delete []Widget;
the brackets tell the compiler it’s
an array of objects, so the compiler generates code to look for the number of
objects in the array and to call the destructor that many times. You can see
that, even though the array operator new( ) and operator
delete( ) are only called once for the entire array chunk, the
default constructor and destructor are called for each object in the
array.