Defective Language

Mismatched Allocation and Delete Nonsense

Efficient yet confused. Powerful but unsafe. So is the nature of C++ object allocation and instantiation.

(This article is part of the series on Defective C++)

Arrays

The first warning of a memory problem in C++ is the need to match array allocation with special deallocation syntax.

int * a = new int[10];
delete a; //wrong
delete[] a; //right

Any call to `new[]` must be matched with a call to `delete[]`. Since the array in C++ is kind of a dimunitive type, becoming a pointer at any instant, it is not possible for the compiler to enforce this requirement, in which case it’d simply be an inconvenience. Instead you’ll just end up getting undefined run-time behaviour, hopefully your program will crash, but likely you’ll just get unusual memory corruption.

Beyond just a syntax issue, this problem is exemplified by templates: you simply can’t easily write a wrapper that works with both plain pointers and arrays. Look at the shared_ptr wrapper class and you’ll notice it can’t be readily used with an array. This is unfortunate since the pattern of a shared pointer doesn’t change whether the underlying pointer is a single object or an array.

Placement New

An important feature of memory management is the ability to instantiate objects at an already allocated location in memory. This is done with the placement new syntax. Deleting this object however requires an entirely different syntax.

T * a = new (block)T;
a->~T();

Use a custom allocator instead and you’ll also be forced to write  an explicit call to operator delete. While the basic new and delete offer a sane symmetry, the placement syntax is extremely asymmetric and confuses exactly what new and delete are supposed to be doing.

Virtual Destructor

Where the array syntax may be considered annoying, and the placement syntax merely confused, it is hard to deny the ability to delete part of an object is critically flawed. Whenever you delete an object through a pointer to one of its bases, you may either properly delete the whole object, or just delete part of it depending on how it was declared.

struct A { ~A() { } };
struct B : public A { ~B() { } };

B * b = new B;
A * a = b;
delete a; //half-delete

Some might still be shocked to learn the above does not call B::~B but only calls A::~A. Unless the base-class destructor is marked virtual the delete operator will only delete the immediately known type. Here that is an A since it is deleting an A*.

Solutions

The history of these problems stems from C compatibility. In particular, a C-struct uses no more memory than its actual member contents (plus padding for ailgnment). In C, memory allocation and object instantiation are two distinct operations which have to be manually performed. C++  merges these two operations, but does so with some critical flaws..

There is no arguing that at times you’ll need fine control of memory allocation and instantiation. Such options should be provided, but the default should be a sane system where delete just does the right thing. That is, delete A will properly delete an array, single object, or derived object. If the object was allocated via a special allocator it should also properly call the deallocator, or otherwise properly deallocate all via that single delete A syntax.

Doing so may require meta-information to be stored along with any allocated object. Regardless of how an object is created the resulting object must contain enough information to say how it should be deleted. This concept isn’t entirely new, just look at any smart_ptr and you’ll see a deleter function which does exactly this.

Curiously, if you also believe a language should be garbage collected you also implicitly support this feature. The collector will have to know exactly how to destroy any object. Short of a full scanning collector, safe deletion also simplifies the task of any object pool or any variant type for that matter.

Categories: Defective Language

Tagged as: , ,

1 reply »

  1. Hey man, interesting to read what irks you about C++, but I have to say that it’s pretty much impossible to implement the solutions:

    delete can’t do everything right, consider the passing of reinterpreted void pointers: the compiler can not deduce whether it’s an array or not. I read that the storage of how things work is pretty much dark magic. So I guess our hand is forced. Also de-allocation of a derived object is normal and succesful I think. The only time one’d need a virtual destructor is when you delete a base pointer that points to the derived.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s