Allocating Arrays with Placement new

llocating objects on a predetermined memory address has become a popular idiom in recent years, particularly in mobile programming, embedded systems, and custom garbage collectors. However, the technique shown in my debut 10-Minute Solution covers only scalar objects. Many readers have asked me whether it’s possible to use a similar technique for allocating arrays as well. The answer is of course “yes.” Here’s how.


How can you allocate arrays of objects on a predetermined memory address?


Use placement new with a twist.

Good Ol’ new
Before discussing array allocations, let’s remind ourselves the basics i.e., how to use placement new. The scalar (i.e. non-array) version placement new takes a user-supplied address on which it constructs an object. Unlike the ordinary new operator, placement new doesn’t allocate storage for the object; it merely constructs it on the memory address given. Here’s an example:

#include  //required for using placement newclass Widget {..};char* buff=new char [sizeof (Widget)];//preallocate storageWidget* pw= new(buff) Widget; //construct Widget on buffpw->Draw(); //use Widget

The destruction of such an object consists of two steps. First, invoke its destructor explicitly:

pw->~Widget(); //explicit destructor invocation

Then, reclaim the raw memory:

delete[] buff;

Array Allocation
Array allocation requires the same steps, albeit with some subtle differences. First, allocate a buffer large enough to hold an array of the desired type:

const int ARRSIZE=5;char * buff=new [sizeof(Widget)*ARRSIZE]; 

Next, construct an array of ARRSIZE objects on the buffer using placement new []:

Widget* pw=new(buff) Widget[ARRSIZE];//construct an array

You can now use this array as usual:

for (int i=0; i

Note: Remember that in order to create an array of X, class X must have an accessible default constructor.

Array Destruction
To destroy such an array, call each element's destructor explicitly:

int i=ARRSIZE;while (i) pw[--i].~Widget()

Notice that the while-loop uses a descending order to preserve the canonical destruction order of C++: the array element with the highest index is destroyed first, because it was constructed last.

Performance Refinements
Placement new[] has a potential performance problem. It initializes every element in the array. When dealing with large arrays, this isn't efficient. In some applications, only a portion of the array is actually used, or the elements are assigned a different value immediately after construction. In such cases, you want to defer (or completely elide) the initialization of array elements. This is possible but requires additional maneuvers.

As usual, start by allocating a raw buffer with the appropriate size. This time however, use the global operator new:

Widget * warr= static_cast  (::operator new ( sizeof(Widget)* ARRSIZE));

The global operator new, very much like C's malloc(), merely allocates raw bytes of memory from the free-store, without initialization. It returns void *, which has to be explicitly cast to a typed pointer using static_cast.

You're probably wondering why I use the global operator new instead of:

new char[sizeof(Widget)*ARRSIZE); //works but not recommended

Although this syntax works too, it would force you to use reinterpret_cast instead of static_cast to convert char * to Widget *.As a rule, prefer static_cast when possible.

At this stage, warr is actually a pointer to raw memory. You can't access its elements because they haven't been initialized yet. To initialize individual elements, call placement new once more, like this:

void assign(Widget arr[],             size_t & sz,             const Widget& initval){ new (&arr[sz++]) Widget (initval); //invoke copy ctor}

How does it work? assign() passes the address of an array element to placement new which invokes Widget's copy constructor, initializing that element with initval. This method enables you to initialize elements selectively, leaving the rest of the array uninitialized. The elements within the range arr[sz]..arr[last] aren't initialized.

To destroy such an array, invoke the destructor of every initialized object. Then call the global operator delete to reclaim the raw storage:

void destroy(Widget arr[], size_t & sz){ while (sz)  arr[--sz].~Widget();//destroy all initialized elements ::operator delete (arr); //reclaim raw storage}

As Good as new
The techniques shown here are quite dangerous. Therefore, they should be encapsulated in higher-level classes that hide the implementation details from users. STL allocators are a good example of this design practice. Under the hood, they use similar techniques to elide object initialization, minimize reallocations, and speed up allocations.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

The Latest

iOS app development

The Future of iOS App Development: Trends to Watch

When it launched in 2008, the Apple App Store only had 500 apps available. By the first quarter of 2022, the store had about 2.18 million iOS-exclusive apps. Average monthly app releases for the platform reached 34,000 in the first half of 2022, indicating rapid growth in iOS app development.

microsoft careers

Top Careers at Microsoft

Microsoft has gained its position as one of the top companies in the world, and Microsoft careers are flourishing. This multinational company is efficiently developing popular software and computers with other consumer electronics. It is a dream come true for so many people to acquire a high paid, high-prestige job

your company's audio

4 Areas of Your Company Where Your Audio Really Matters

Your company probably relies on audio more than you realize. Whether you’re creating a spoken text message to a colleague or giving a speech, you want your audio to shine. Otherwise, you could cause avoidable friction points and potentially hurt your brand reputation. For example, let’s say you create a