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
}