The new operator in C++
Written by Eli Bendersky   
Tuesday, 29 March 2011
Article Index
The new operator in C++
Allocation
Deleting an allocated object

Allocation - an example

It’s educational to look at the way the new operator works in C++. Allocation is a two step process:

  1. First, raw memory is requested from the OS, represented by the global operator new function.
  2. Once that memory is granted, the new object is constructed in it.

The C++ FAQ presents a really nice code sample I’d like to reproduce here:

When you write this code:

Foo* p = new Foo();

what the compiler generates is functionally similar to:

Foo* p;

// don't catch exceptions thrown by the 
// allocator itself void* raw = operator new(sizeof(Foo)); // catch any exceptions thrown by the ctor try { p = new(raw) Foo(); // call the ctor
//with raw as this } catch (...) { // oops, ctor threw an exception operator delete(raw); throw;
// rethrow the ctor's exception }

The funny syntax inside the try statement is called "placement new", and we’ll discuss it shortly. For completeness’ sake, let’s see a similar breakdown for freeing an object with delete, which is also a two-step process:

  1. First, the destructor of the object that’s being deleted is called.
  2. Then, the memory occupied by the object is returned to the OS, represented by the global operator delete function.

So:

delete p;

Is equivalent to:

if (p != NULL) {
 p->~Foo();
 operator delete(p);
}

Note the check for NULL. It's the reason for delete p being safe even when p is NULL – another C++ FAQ.

This is also a good place to repeat something I’ve mentioned in the first section of this article – if a class has its own operator new or operator delete, these get invoked instead of the global functions when an object is allocated or deallocated.

Placement new

Now, back to that "placement new" we saw in the code sample above. It happens to be a real syntax we can use in our C++ code. First, I want to briefly explain how it works. Then, we’ll see when it can be useful.

Calling placement new directly skips the first step of object allocation. We don’t ask for memory from the OS. Rather, we tell it where there’s memory to construct the object in. Remember, to ensure that the location that pointer goes to has enough memory for the object, and that the pointer is also correctly aligned. The following code sample should clarify this:

int main(int argc, const char* argv[])
{
 // A "normal" allocation. Asks the OS
// for memory, so we don't actually
// know where this ends up pointing. int* iptr = new int; cerr << "Addr of iptr =" << iptr << endl;
// Create a buffer large enough to // hold an integer, note its address.
char mem[sizeof(int)]; cerr << "Addr of mem = " << (void*) mem << endl; // Construct the new integer
// inside the buffer 'mem'. // The address is going to be mem's.
// int* iptr2 = new (mem) int; cerr << "Addr of iptr2 = " << iptr2 << endl; return 0; }

For a particular run on my machine it prints:

Addr of iptr = 0x8679008
Addr of mem = 0xbfdd73d8
Addr of iptr2 = 0xbfdd73d8

As you can see, the mechanics of placement new are quite simple. What’s more interesting is the question – why would we need something like this? It turns out placement new is quite useful in a few scenarios:

  • Custom non-intrusive memory management. While overloading operator new for a class also allows custom memory management, the key concept here is non-intrusive. Overloading operator new requires you to change the source code of a class. But suppose we have a class the code of which we don’t want or can’t change. How can we still control its allocation? Placement new is the answer here. A common programming technique that uses placement new for this purpose is memory pools, sometimes also called "arenas" (Memory pools is a large and fascinating topic that I can’t cover it in any meaningful depth here. So I encourage you to look up more information such as Wikipedia's)
  • In some applications it is necessary to allocate objects in specific memory regions. One example is shared memory. Another is embedded applications or drivers with memory mapped peripherals, which can be controlled conveniently by objects allocated "on top" of them.
  • Many container libraries pre-allocate large buffers of memory. When new objects are added they have to be constructed in these buffers, so placement new is used. The prime example is probably the standard vector container.


Last Updated ( Tuesday, 29 March 2011 )
 
 

   
RSS feed of all content
I Programmer - full contents
Copyright © 2014 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.