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; } }