Let us examine the possibilities of new and delete.
The new operator throws std::bad_alloc, when the allocation fails.
namespace std
{
class bad_alloc : public exception { /* ... */ };
}
// normal new and delete throws bad_alloc
void* operator new(size_t) throw(bad_alloc);
void operator delete(void *) throw();
However exceptions are not always the favorite solution. The nothrow version of new operation returns null-pointer rather throwing exception.
// indicator for allocation that doesn't throw exceptions
struct nothrow_t {};
extern const nothrow_t nothrow;
// what to do, when error occurs on allocation
typedef void (*new_handler)();
new_handler set_new_handler(new_handler new_p) throw();
// nothrow version
void* operator new(size_t, const nothrow_t&) throw();
void operator delete(void*, const nothrow_t&) throw();
There are separate operators for arrays:
// the same for arrays void* operator new[](size_t) throw(bad_alloc); void operator delete[](void*) throw(); void* operator new[](size_t, const nothrow_t&) throw(); void operator delete[](void*, const nothrow_t&) throw();
Sometimes we want to create objects (and call constructor) without allocating new memory are. The placement version of new operator does exactly this.
// placement new and delete
void* operator new(size_t, void* p) throw() { return p; }
void operator delete(void* p, void*) throw() { }
void* operator new[](size_t, void* p) throw() { return p; }
void operator delete[](void* p, void*) throw() { }
This is a way to optimalize running time. This is the original code:
using namespace std;
int main()
{
for( long i = 0; i < 10000; ++i)
{
for ( long j = 0; j < 10000; ++j)
{
date *dp = new date(2001,1,1);
// ...
delete dp;
}
}
return 0;
}
real 0m31.519s
user 0m31.510s
sys 0m0.000s
And this is the optimalized code using placement new:
#include <iostream>
#include "date.h"
using namespace std;
int main()
{
char *cp = new char[sizeof(date)];
for( long i = 0; i < 10000; ++i)
{
for ( long j = 0; j < 10000; ++j)
{
date *dp = new(cp) date(2001,1,1);
// ...
dp->~date();
}
}
delete [] cp;
return 0;
}
real 0m8.765s
user 0m8.760s
sys 0m0.000s
New and delete operators could be defined by the user in two levels:
As a static memberfunction of a class
As a global operator
The member new and delete is responsible for allocating objects for that certain class. The global operator however is responsible for all memory allocation.
include <iostream>
#include <new>
#include <memory>
using namespace std;
struct node
{
node( int v) { val = v; left = rigth = 0; }
void print() const { cout << val << " "; }
/* static */ void *operator new( size_t sz) throw (bad_alloc);
/* static */ void operator delete(void *p) throw();
int val;
node *left;
node *right;
};
// member new and delete
void *node::operator new( size_t sz) throw (bad_alloc)
{
std::cerr << "node::new" << std::endl;
return ::operator new(sz);
}
void node::operator delete(void *p) throw()
{
std::cerr << "node::delete" << std::endl;
::operator delete(p);
}
// global new and delete
void *operator new( size_t sz) throw (bad_alloc)
{
// not for MSVC++ !
std::cerr << "::new" << std::endl;
return malloc(sz);
}
void operator delete( void *p) throw ()
{
// not for MSVC++ !
std::cerr << "::delete" << std::endl;
free(p);
}
void insert( node *&r, int v); // insert into (sub)tree
void print( node *r); // print (sub)tree
void destroy( node *&r ); // destroy (sub)tree
int main()
{
node *root = 0;
int i;
while ( cin >> i )
{
insert( root, i);
}
print( root);
destroy( root);
return 0;
}
// ...
As we can overload new and delete operators at class or global level: we can provide extra parameters, for debug or other purposes. This example explain the details.
#include <iostream>
#include <new>
#include <memory>
using namespace std;
struct node
{
node( int v);
void print() const;
static void *operator new( size_t sz, int i) throw (bad_alloc);
static void operator delete(void *p) throw();
int val;
node *left;
node *right;
};
// members
node::node( int v) // the constructor
{
left = right = 0;
val = v;
}
void node::print() const // a const member function
{
cout << val << " ";
}
// global new and delete
void *operator new( size_t sz, int i) throw (bad_alloc)
{
// not for MSVC++ !
std::cerr << "::new(int): " << i << std::endl;
return malloc(sz);
}
void operator delete( void *p, int i) throw ()
{
// not for MSVC++ !
std::cerr << "::delete(int): " << i << std::endl;
free(p);
}
// global new and delete
void *operator new( size_t sz) throw (bad_alloc)
{
// not for MSVC++ !
std::cerr << "::new" << std::endl;
return malloc(sz);
}
void operator delete( void *p) throw ()
{
// not for MSVC++ !
std::cerr << "::delete" << std::endl;
free(p);
}
// member new and delete
void *node::operator new( size_t sz, int i) throw (bad_alloc)
{
std::cerr << "node::new: " << i << std::endl;
return ::operator new(sz,i);
}
void node::operator delete(void *p) throw()
{
node *np = reinterpret_cast<node *>(p);
std::cerr << "node::delete: " << np->val << std::endl;
::operator delete(p,np->val);
}
void insert( node *&r, int v); // insert into (sub)tree
void print( node *r); // print (sub)tree
void destroy( node *&r ); // destroy (sub)tree
int main()
{
node *root = 0;
int i;
while ( cin >> i )
{
insert( root, i);
}
print( root);
destroy( root);
return 0;
}
void print( node *r)
{
if ( r )
{
print( r->left);
r->print();
print( r->right);
}
}
void insert( node *&r, int v)
{
if ( r )
{
insert( v < r->val ? r->left : r->right, v);
}
else
{
r = new(v) node(v);
}
}
void destroy( node *&r)
{
if ( r )
{
destroy( r->left);
destroy( r->right);
delete r;
r = 0;
}
}