Placement New



Item 35. Placement New

It's impossible to call a constructor directly. However, we can trick the compiler into calling a constructor for us through the use of placement new.

void *operator new( size_t, void *p ) throw()
    { return p; }

Placement new is a standard, global, overloaded version of operator new that cannot be replaced with a user-defined version (unlike the standard, global, "usual" operator new and operator delete that can be replaced but probably shouldn't be). The implementation ignores the size argument and returns its second argument. This has the effect of allowing one to "place" an object at a particular location, giving the effect of being able to call a constructor.

class SPort { ... }; // represents a serial port
const int comLoc = 0x00400000; // location of a port
//...
void *comAddr = reinterpret_cast<void *>(comLoc);
SPort *com1 = new (comAddr) SPort; // create object at comLoc

It's important to distinguish the new operator from functions that are named operator new. The new operator can't be overloaded and so always behaves in the same way; it calls a function named operator new and then initializes the returned storage. Any variation of behavior we achieve with memory allocation has to do with different, overloaded versions of operator new, not with the new operator. The same applies to the delete operator and operator delete.

Placement new is a version of the function operator new that doesn't actually allocate any storage; it just returns a pointer to some storage that (presumably) is already allocated. Because no storage is allocated by a call to placement new, it's important not to delete it.

delete com1; // oops!                             

However, even though we didn't allocate any storage, we did create an object, and that object should be destroyed at the end of its lifetime. We avoid the delete operator and instead call the object's destructor directly:

com1->~SPort(); // call dtor but not operator delete

Designs that involve direct destructor invocation tend to be prone to error, however, often resulting in multiple destruction of the same object or no destruction of an object. We would typically prefer to employ such designs only when necessary, in well-hidden and well-maintained areas of our code.

We also have a placement array new that can be used to create an array of objects at a given location:

const int numComs = 4;
//...
SPort *comPorts = new (comAddr) SPort[numComs]; // create array

Of course, these array elements must eventually be destroyed:

int i = numComs;
while( i )
    comPorts[--i].~SPort();

One potential problem with arrays of class objects is that each element must be initialized by a call to a default constructor when the array is allocated. Consider a simple, fixed-size buffer to which one can append a new value:

string *sbuf = new string[BUFSIZ]; // BUFSIZ default ctor calls!  
int size = 0;                                                     
void append( string buf[], int &size, const string &val )         
    { buf[size++] = val; } // wipe out default initialization!    

If only a portion of the array is used, or if the elements are immediately assigned, this is inefficient. Worse, if the element type of the array has no default constructor, we'll get a compile-time error.

Placement new is often used to address this buffer problem. With this approach the storage for the buffer is allocated in such a way as to avoid initialization by the default constructor (if any).

const size_t n = sizeof(string) * BUFSIZE;
string *sbuf = static_cast<string *>(::operator new( n ));
int size = 0;

We can't assign to an array element on its first access because it hasn't been initialized (see Assignment and Initialization Are Different [12, 41]). Instead, we use placement new to initialize the element with the copy constructor:

void append( string buf[], int &size, const string &val )
    { new (&buf[size++]) string( val ); } // placement new

As usual, with placement new we are required to do our own cleanup:

void cleanupBuf( string buf[], int size ) {
    while( size )
        buf[--size].~string(); // destroy initialized elements
    ::operator delete( buf ); // free storage
}

This approach is fast, clever, and not intended for viewing by the general public. This basic technique is used extensively (in a more sophisticated form) in most implementations of the standard library containers.