Sunday, May 24, 2009

C++ Placement New/Delete Operator

In C++, operators new/delete mostly replace the use of malloc() and free() in C. For example:

class A
{
public:
A();
~A();
};

A *p = new A;

...

delete p;


allocates storage for an A object and arranges for its constructor to be called, later followed by invocation of the destructor and freeing of the storage. You can use the standard new/delete functions in the library, or define your own globally and/or on a per-class basis.

There's a variation on new/delete worth mentioning. It's possible to supply additional parameters to a new call, for example:

A *p = new (a, b) A;

where a and b are arbitrary expressions; this is known as "placement new". For example, suppose that you have an object instance of a specialized class named Alloc that you want to pass to the new operator, so that new can control allocation according to the state of this object (that is, a specialized storage allocator):

class Alloc {/* stuff */};

Alloc allocator;

...

class A {/* stuff */};

...

A *p = new (allocator) A;

If you do this, then you need to define your own new function, like this:

void* operator new(size_t s, Alloc& a)
{
// stuff
}

The first parameter is always of type "size_t" (typically unsigned int), and any additional parameters are then listed. In this example, the "a" instance of Alloc might be examined to determine what strategy to use to allocate space. A similar approach can be used for operator new[] used for arrays.

This feature has been around for a while. A relatively new feature that goes along with it is placement delete. If during object initialization as part of a placement new call, for example during constructor invocation on a class object instance, an exception is thrown, then a matching placement delete call is made, with the same arguments and values as to placement new. In the example above, a matching function would be:

void operator delete(void *p, Alloc &a)
{
// stuff
}


With new, the first parameter is always "size_t", and with delete, always "void*". So "matching" in this instance means all other parameters match. "a" would have the value as was passed to new earlier.

Here's a simple example:

int flag = 0;

typedef unsigned int size_t;

void operator delete(void *p, int i)
{
flag = 1;
}

void* operator new(size_t s, int i)
{
return new char[s];
}

class A
{
public:
A() {throw -37;}
};

int main()
{
try
{
A *p = new (1234) A;
}
catch (int i)
{
}
if (flag == 0)
return 1;
else
return 0;
}

Placement delete may not be in your local C++ compiler as yet. In compilers without this feature, memory will leak. Note also that you can't call overloaded operator delete directly via the operator syntax; you'd have to code it as a regular function call.

In addition to the language providing this general capability, the C++ standard library also provides a specific instance for void*:

void* operator new(size_t, void*);

void operator delete(void*, void*);

These are accessed by saying:

#include

These functions are defined to do nothing (though new returns its argument). Their purpose is to allow construction of an object at a specific address, which is often useful in embedded systems and other low-level applications:

const unsigned long MEMORY_MAP_IO_AREA = 0xf008;

...

Some_Class* p = new ((void*) MEMORY_MAP_IO_AREA) Some_Class();

Based on a fairly recent decision of the standards committee, this definition of placement new/delete for void* is reserved by the library, and cannot be replaced by the user (unlike the normal global operator new, which can be). The library also defines a similar placement new/delete for allocating arrays at a specific address.

No comments: