Friday, November 14, 2003

Allocation vs Initialization in C++

C++ gives you an amazing amount of control. One case of this is the allocation/initialization process. One of the advantages C++ has over C, is that it not only guarantees that a block of memory will be allocated, but also that the constructor will run automatically. A similar guarantee is made for deallocation and destruction as well.

When you say:

{
Foo f; // memory allocated & default constructor called
} // destructor called and memory deallocated

C++ allocates enough memory on the stack the size of Foo, and calls Foo's default constructor, giving those bits an identity (Foo). When it goes out of scope, it calls the destructor and deallocates that memory.

Same thing happens with creating objects dynamically on the heap...

Foo* p = new Foo; // memory allocated & default constructor called

Except now, you have to explicitly delete this object...

delete p; // destructor called & memory deallocated

The interesting thing is that there is a difference between keyword new and operator new. operator new is responsible for allocating the memory, while keyword new calls the constructor. So, you could call operator new directly and NOT have the constructor called.

Foo* p = static_cast( operator new( sizeof( Foo ) ) );

This will allocate memory on the heap for a Foo WITHOUT constructing it.

What's the point? Well, not much when you're dealing with individual objects, but they come in handy with arrays of objects.

Consider this line...

Foo* p = new Foo[ 10 ];

This will not only allocate memory for 10 Foos, but also call the default constructor for each of those Foo's. Allocation takes place in constant time O(1), initialization is linear O(N).

What if you wanted a different constructor to be called for each object? What if you didn't want the default? There is no way to specify which constructor you want called when dealing with arrays. You'd have an array of 10 default constructed Foos. You would then have to traverse it, delete those objects, create newly constructed individual objects and assign it to each index.

This is where operator new comes into play.

Foo* p = static_cast( operator new( 10 * sizeof( Foo ) );

for ( int i = 0; i < 10; ++i )
{
new ( &p[ i ] ) Foo( 1, 2 );
}

The first line allocates memory for 10 Foos. That's all it does. The loop goes through each index of the array and constructs a Foo with a 2 argument constructor.

The Standard Template Library actually has a class called allocator which encapsulates this functionality. Methods on that class are allocate(), construct(), destory() and deallocate().

So you can do something like...

Foo* p = allocator.allocate( 10 ); // allocate memory for 10 Foos

for ( int i = 0; i < 10; ++i )
{
allocator.construct( &p[ i ], Foo( 1, 2 ) ); // construct Foo with two argument constructor
}

for ( int i = 0; i < 10; ++i )
{
allocator.destroy( &p[ i ] ); // destroy Foo
}

allocator.deallocate( p ); // deallocate memory

There are a ton of minute details like this in C++. Because it gives you so much control it is such an interesting language.

1 comment:

varnie said...

good and useful article, but maybe there had to be:

Foo* p = static_cast< Foo * >( operator new( sizeof( Foo ) ) );

instead of:

Foo* p = static_cast( operator new( sizeof( Foo ) ) );

?
at least, GCC doesn't understand yours example. thanks for article once more, good bye.