New and Delete

Let us examine the possibilities of new and delete.

Operators

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

Overloading the New and Delete operators

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

Extra Parameters for New and Delete

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