Friday, April 3, 2009

C++ Placement New operator

Abstract:


These are some notes I have on C++'s operator new.I will point out how to avoid using operator new at all. More general constructs in the language can achieve similar objectives with more flexibility. You may find the replacement allocator proposed here fairly disgusting. Just keep in mind that something far worse is built right into the language.

New operator Life cycle:
The new operator provides dynamic storage allocation.The new operator is used to find storage in free store for the object being created. The new expression returns a pointer to the object created and can be used to initialize the object. If the object is an array, a pointer to the initial element is returned.You can use set_new_handler() only to specify what new does when it fails.
When the object being created is an array, only the first dimension can be a general expression. All subsequent dimensions must be constant integral expressions. The first dimension can be a general expression even when an existing type is used. You can create an array with zero bounds with the new operator. For example:

char * pcharzero = new char[0];
In this case, a pointer to a unique object is returned.
If An object created with the help of operator new() or operator new[](). object exists until the operator delete() or operator delete[]() is called to deallocate the object's memory. A delete operator or a destructor will not be implicitly called for an object created with a new that has not been explicitly deallocated before the end of the program.

Placement New operator:
Operator new allocates memory from the heap, on which an object is constructed. Standard C++ also supports placement new operator(allocator function), which constructs an object on a pre-allocated buffer. This is useful when building a memory pool, a garbage collector or simply when performance and exception safety are paramount (there's no danger of allocation failure since the memory has already been allocated, and constructing an object on a pre-allocated buffer takes less time):

static const int MAX_SIZE = 10 ;

int* buffer = new int[MAX_SIZE] ;

inline void* operator new( size_t sz, void* buff )
{ return buff ; }
inline void* operator new[]( size_t sz, void* buff )
{ return buff ; }

for( int i = 0; i < MAX_SIZE; i++ )
{
int* p = new (buffer+i) int(i) ;
// specify where you want the object
}

Use of Placement:
Placement syntax has four main uses: default placement, preventing exceptions, custom allocators, and debugging.

Default Placement:
The placement overloads of operator new and operator delete that employ an additional void * parameter are used for default placement, also known as pointer placement.If you need a certain object at a specific hardware address use placement. It is also required for the construction of objects that need to reside in a certain memory area, such as an area that is shared between several processors of a multiprocessor computer.

Preventing exceptions:

Normally,the new functions throw an exception,if they encounter an error of type std::bad_alloc,such as exhaustion of all available memory.If you wish in your program not to throw an exception. Programmer must has to include the standard C++ library header in source code. For example:

typedef struct userobj
{
;
};
int main (void)
{
// Call the function operator new(size_t, const nothrow_t &) and (if successful) construct the object.
userobj * ptr = new (std::nothrow) userobj ;
if (ptr) {
// The storage has been allocated and the constructor called.
cout<<"storage has been allocated for ptr="<<<<"Allocation Error"<, programmers has to write their own allocation and deallocation functions, overloaded for user-defined types. For example, one could define a memory management class as follows:


class Allocator {
public:
void * allocate ( size_t ) ;
void deallocate ( void * ) ;
} ;

define custom placement allocation and deallocation functions as follows:

void *
operator new (size_t size, Allocator & arena)
{
return arena.allocate(size) ;
}
void
operator delete (void * ptr, Allocator & arena)
{
arena.deallocate(ptr) ;
}

The program would employ the placement syntax to allocate objects using different instances of the Allocator class as follows:

Allocator first_a, second_a ;
Type * p1 = new (first_a) Type ;
Type * p2 = new (second_a) Type ;

Destroying an object whose storage is allocated in such a fashion requires some care. Because there is no placement delete expression, one cannot use it to invoke the custom deallocator. One must either write a destruction function that invokes the custom deallocator, or call the placement delete function directly, as a function call.

The former would resemble:

void
destroy (Type * p, Allocator & arena)
{
p->~Type() ; // First invoke the destructor explicitly.
arena.deallocate(p) ; // Then call the deallocator function directly.
}

which would be invoked from a program as:

Allocator arena ;
Type * p = new (arena) Type ;
/* ... */
destroy(p, arena) ;

The latter would involve simply writing the destructor invocation and delete function call into the program:

Allocator arena ;
Type * p = new (arena) Type ;
/* ... */
p->~Type() ; // First invoke the destructor explicitly.
operator delete(p, arena) ; // Then call the deallocator function indirectly via operator delete(void *, Allocator &) .

A common error is to attempt to use a delete expression to delete the object. This results in the wrong operator delete function being called. Dewhurst recommends two strategies for avoiding this error. The first is to ensure that any custom allocators rely upon the Standard C++ library's global, non-placement, operator new, and are thus nothing more than simple wrappers around the C++ library's memory management. The second is to create new and delete functions for individual classes, and customize memory management via class function members rather than by using the placement syntax.

Debugging :

Placement new can also be used as a simple debugging tool, to enable programs to print the filename and line number of the source code where a memory allocation has failed. This does not require the inclusion of the Standard C++ library header , but does require the inclusion of a header that declares four placement functions and a macro replacement for the new keyword that is used in new expressions. For example, such a header would contain:

#if defined(DEBUG)
void * operator new (size_t size, const char* file, int line);
void * operator new[] (size_t size, const char* file, int line);
void operator delete (void * p, const char* file, int line);
void operator delete[] (void * p, const char* file, int line);
#define New new(__FILE__, __LINE__)
#else
#define New new
#endif

This would be employed in a program as follows:

Type * p = New Type ;

The custom-written placement new functions would then handle using the supplied file and line number information in the event of an exception. For example:

#include

class Error {
public:
Error(const char * file, int line) { /* ... */ }
/* ... */
} ;

void *
operator new (size_t size, const char* file, int line)
{
if (void * p = ::operator new (size, std::nothrow))
return p ;
throw Error(file, line) ;
}

Placement delete:

To avoid memory leak the placement delete functions are called.As noted above, there is no placement delete expression. It is not possible to call any placement operator delete function using a delete expression.

A placement new expression first calls the placement operator new function, then calls the constructor of the object upon the raw storage returned from the allocator function. If the constructor throws an exception, it is necessary to deallocate that storage before propagating the exception back to the code that executed the placement new expression, and that is the purpose of the placement delete functions.

The placement delete function that is called matches the placement new function that was invoked by the placement new expression. So, Example to implement placement delete fun which is called by operator delete(void *, const A &)


struct A {} ;
struct E {} ;

class Type {
public:
Type() { throw E() ; }
} ;

void * operator new ( size_t, const A & ) ;
void operator delete ( void *, const A & ) ;

int main ()
{
A a ;
Type * p = new (a) Type ;
return 0 ;
}

This is why the pointer placement delete functions are defined as no-operations by the Standard C++ library. Since the pointer placement new functions do not allocate any storage, there is no storage to be deallocated in the event of the object's constructor throwing an exception.[6]

If no matching placement delete function exists, no deallocation function is called in the event of an exception being thrown by a constructor within a placement new expression. There are also some (older) C++ implementations that do not support placement delete (which, like the exception-throwing allocator functions, were an addition made to C++ when it was standardized) at all. In both such situations, an exception being thrown by a constructor when allocating using a custom allocator will result in a memory leak.



No comments: