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

void* operator new(size_t)    throw(bad_alloc); // new() may throw bad_alloc
void  operator delete(void *) throw();          // delete() never throws



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)
        {
            X *xp = new X(i,j);
            // ...
            delete xp;
        }
    }
    return 0;
}


The optimized version using placement new


#include <iostream>
#include <new>

using namespace std;

int main()
{
    char *cp = new char[sizeof(X)];
    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;
}



Overloading the New and Delete operators

New and delete operators could be defined by the user in two levels:

- a static memberfunction of a class
- 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 as static member
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
explains 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;
    }
}



Objects with Special Storage Restrictions

Sometimes restriction of object storage is usefull.


Objects only in the Heap

The following class could be created only by new operator in the heap.
This is important when new takes extra parameters, or defines special
semantics: like persistency or garbage collection.


//
//  Objects only in heap:
//

#include <new>

class X
{
public:
    X() {}
    void destroy() const { delete this; }
protected:
    ~X() {}
};

class Y : public X { };
//  class Z { X xx; };  // use pointer!


The usage:

X x1;    // syntax error
int main()
{
    X x2;          // syntax error
    Y y1;          // syntax error if ~X() was private
    X* xp = new X;
    Y* yp = new Y;

    delete xp;     // syntax error
    xp->destroy();
    delete yp;     // syntax error if ~X() was private
};



It is still possible to create objects of X and Y in the stack or 
as static variable. We can define placement new the same way as we
did with new.



Objects not in the Heap


#include <new>

class X
{
private:
    static void *operator new( size_t);
    static void operator delete(void *);
};

class Y : public X { };


Usage

X x1;
int main()
{
    X x2;
    Y y1;
//  X* xp = new X;
//  Y* yp = new Y;

//  delete xp;
//  delete yp;
};


Arrays are not Polymorphic

In object-oriented programming still not all the objects are polymorphic.
(And they are not polymorphic in Java too.)


#include <iostream>

using namespace std;

struct Base
{
    Base() { cout << "Base" << " "; }
    virtual ~Base() { cout << "~Base" << endl; }

    int i;
};
struct Der : public Base
{
    Der() { cout << "Der" << endl; }
    virtual ~Der() { cout << "~Der" << " "; }

    int it[10]; // sizeof(Base) != sizeof(Der)
};

int main()
{
    Base *bp = new Der;
    Base *bq = new Der[5];

    delete    bp;
    delete [] bq;   // this causes runtime error
}