Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Reflections on the delete vs. delete[ ] Split

Last updated Jan 1, 2003.

The difference between delete and delete[] is straightforward -- the former destroys a scalar object and the latter destroys an array. Yet the question still remains: why does C++ really need this split between a scalar and an array version of the same operator, especially when this leads to bugs and design compromises? In this column I will explain the historical grounds of this split and discuss its impact on state-of-the-art C++ programming and design.

Back in C

C programmers who migrate to C++ are often surprised to learn about the delete vs. delete[] split. In C, it doesn’t matter whether you’re allocating a single object using malloc()(here "object" is used in its wider sense of course, i.e., a chunk of memory) or an array of objects using calloc() -- when you destroy those objects, you call free() and that’s it. However, this superficial observation isn’t fair. The dynamic allocation functions of standard C know nothing about class objects’ semantics; they merely allocate chunks of raw memory. In C++, new and its array counterpart new[] do more than this: in addition to allocating raw storage, new and new[] initialize the allocated object(s) by invoking their constructor(s). In a similar vein, delete and delete[] don’t just reclaim raw memory; they are also responsible for invoking the destructor(s) of the allocated objects.

There is another observation that we may have missed: C’s allocation functions never have to worry about the distinction between a scalar object and an array because they always deal with arrays -- arrays of bytes. In a way, you can think of malloc() as a function that’s implemented like this:

void * malloc(size_t sz)
{
 return new char[size];
}

I’m not aware of any standard C library that is implemented in this manner but this example illustrates the point.

Pre-standard C++

In the early days of C++, delete[] was a binary operator taking two arguments. In addition to the pointer to the array, the programmer had to specify the array’s size in the square brackets:

p=new Widget[sz];
delete[sz] p; //antiquated C++

You should be grateful that this interface didn’t last. Forcing programmers to keep track of the size of a dynamically allocated array is painful and dangerous. In many applications, the function that calls delete[], e.g. a class’ destructor, doesn’t know what the array’s size is; it only know that its sole argument is a pointer to an array. Furthermore, a programmer might mistakenly pass the wrong size of the array to delete[]. C++ creators realized this and decided to eliminate the size argument. However, they have kept the distinction between delete and delete[]. Programmers still have to be careful not to use delete instead of delete [] or vice versa.

State of the art C++

Admittedly, C++ code that allocates and deallocates objects directly is becoming rare these days. Well-designed apps use smart pointers, STL containers and other techniques for encapsulating the gory details of memory management. Therefore, one could argue that collapsing delete and delete[] into a single operator is no longer as important as it might have been 20 years ago. I agree with this analysis -- new code that allocates memory directly instead of using more advanced techniques is indeed an alarming sign. However, the split between delete and delete[] is still apparent in other domains. Take for instance smart pointers. Whether it’s bad old auto_ptr or the more palatable shared_ptr, you still need to ensure that these smart pointers own a scalar object and never an array. The reason is that their destructors ultimately calls delete, never delete[], to release the owned object. Thus, if you want a smart pointer that holds an array of objects, you need to use a special smart pointer class specifically designed for arrays. Notice that the array smart pointer class is no substitute for the scalar smart pointer class. Rather, they are mutually exclusive because the array smart pointer class will always call delete[].

This leads us to an interesting question in language design: suppose we were allowed to redesign new and delete according to C’s malloc() and calloc(). That is, we could remove scalar new and delete from the language and use the private case of new[1] and delete[1] instead. What would be the consequences of such a radical change?

Consider the expressions delete p; and delete[] p;. In the latter case, the operator first looks for a cookie that contains the size of the array. The cookie is typically stored as a fixed negative offset from p, so something like this is required to retrieve it:

int sz= *(((char *) p)-sizeof (int));

In reality, this code is typically implemented very efficiently in a few assembly directives. We can redesign delete so that it always reads the cookie, even if p is bound to a single object (assuming of course that new always writes this cookie).

However, do we really want this? I’m not sure. Take for example shared_ptr. It is a high level smart pointer class. However, it’s flexibility already exacts a toll in terms of its size and its runtime performance. As such, you often face the dilemma of choosing between dumb but efficient raw pointers or smart but slower shared_ptr. If we modified shared_ptr so that it could cope with both scalar and array types, the result would be a fatter interface and slower execution time. For example, suppose you store a single object in shared_ptr but your application accidentally treats it as if it were an array of objects:

myshared_ptr[0]->func();

Clearly, the underlying machinery has to catch runtime anomalies such as these. Alas, these runtime validations, range checking and exception handling would be too expensive for many applications. More importantly, think of the usage of such a hypothetical dual purpose smart pointer class. A function that takes a shared_ptr object would need to know whether that smart pointer owns a single object or an array in order to access its elements:

if (sptr.isarray())
 do array stuff
else
 do scalar stuff

This code is pretty ugly, and hard to maintain. At this stage you will probably agree with me that the separation between scalar types and arrays is so fundamental in C++, that the current state of affairs is the most plausible compromise after all: scalar smart pointers will stick to delete and array smart pointers and containers will stick to delete[].