Understanding C++ Placement Syntax

Recently, the group reading activity has given me a deeper understanding of placement new and placement delete.

About the new Expression

C++ provides the <span>new</span> keyword and the <span>delete</span> keyword, which are used for allocating and releasing memory space, respectively. The syntax of the <span>new</span> expression is as follows:

new new-type-id ( optional-initializer-expression-list )

The book states that the <span>new</span> expression does two things:

  • • Allocates a block of memory on the heap, the size of which is equal to <span>sizeof(new-type-id)</span>
  • • Constructs an object in the allocated space, i.e., calls the object’s constructor

Additionally, I learned that if a user wants to construct an object in a custom memory space, they can call another <span>new</span> expression, with the following syntax:

new ( expression-list ) new-type-id ( optional-initializer-expression-list )

Specifically, it is used like this:

void *buffer = malloc(sizeof(ClassA));
ClassA *ptr = new(buffer)ClassA();

The second type of <span>new</span> expression is a special case of the first, only needing to complete step 2, while leaving step 1 to the user.

About the delete Expression

The syntax of the delete expression provided by C++ is:

delete type_pointer;

The book states that delete performs two actions:

  • • Calls the object’s destructor,
  • • Releases the memory space occupied by the object back to the system

It is particularly important to note that setting <span>type_pointer</span> to NULL allows the delete expression to function correctly, but calling <span>delete</span> on the same pointer multiple times will lead to undefined behavior.

Standard operator new and operator delete

The standard new expression in C++ internally calls the standard <span>operator new()</span>, which is defined as follows:

void * operator new (std::size_t) throw(std::bad_alloc);

<span>operator new()</span> is an operator or function that performs standard allocation, i.e., memory allocation.

Similarly, the standard delete expression in C++ internally calls the standard <span>operator delete()</span>, which is defined as follows:

void operator delete (void *) throw();

<span>operator delete()</span> is also an operator or function that performs standard deallocation, i.e., memory release.

Placement new and placement delete

The standard new expression in C++ can fulfill most needs, but not all. For example, how to create an object in existing memory space cannot be done with the standard new expression, and C++ does not support directly calling an object’s constructor in raw memory. Thus, placement new was created, named <span>placement</span>, which also indicates its historical origin, that is, to construct a new object in place.

Of course, creating objects in place is just one part; placement new has a broader extension, and both placement new expression and placement <span>operator new()</span> are generally referred to as placement new, which confuses the concepts.

What is placement <span>operator new()</span>? It is primarily a function and an overloaded version of the standard <span>operator new()</span>. Wiki defines it as follows:

The “placement” versions of the new and delete operators and functions are known as placement new and placement delete.

What is placement new expression? It belongs to C++ syntax, similar to the standard new expression, but internally calls the corresponding <span>placement operator new()</span>. Wiki defines it as follows:

Any new expression that uses the placement syntax is a placement new expression.

Just as the standard new expression calls the standard <span>operator new()</span>, the placement new expression calls the placement version of the <span>operator new()</span>. It seems straightforward, but when mixed together, it doesn’t seem to be a problem. However, consider placement delete, which indicates the placement <span>operator delete()</span><span> function, because <strong>there is fundamentally no placement delete expression</strong>. When we call</span>

delete pObject;

we are calling the standard <span>operator delete()</span>. Why is there no placement delete expression? The creators of C++ explained:

The reason that there is no built-in “placement delete” to match placement new is that there is no general way of assuring that it would be used correctly.

Since there is no placement delete expression, why do we still need placement <span>operator delete()</span><span>? An important reason is that C++ requires placement </span><code><span>operator delete()</span><code> and placement <code><span>operator new()</span> to be paired. Suppose a situation arises:

When you call the placement new expression to construct an object, and an exception is thrown in the constructor, what should be done? C++ can only call the corresponding placement <span>operator delete()</span><code>, to release the memory resources obtained by placement <code><span>operator new()</span>, otherwise, there will be a memory leak. You can feel this through the following example:

#include <cstdlib>
#include <iostream>

struct A {} ;
struct E {} ;

class T {
public:
    T() { throwE() ; }
} ;

void * operator new ( std::size_t, const A & )
    {std::cout << "Placement new called." << std::endl;}
void operator delete ( void *, const A & )
    {std::cout << "Placement delete called." << std::endl;}

int main ()
{
    A a ;
    try {
        T * p = new (a) T ;
    } catch (E exp) {std::cout << "Exception caught." << std::endl;}
    return0 ;
}

Placement operator new() and placement operator delete()

The above only defines the difference between expression and operator. What kind of function qualifies as placement <span>operator new()</span> and placement <span>operator delete()</span>? They differ from the standard <span>operator new()</span> and <span>operator delete()</span><code> by adding custom parameters. However, they must adhere to the following rules.

For placement <span>operator new()</span>, its first function parameter must be <span>std::size_t</span>, indicating the size of the memory to be allocated; for placement <span>operator delete()</span>, its first function parameter must be <span>void *</span>, indicating the pointer to the object to be released. For example:

void * operator new (std::size_t) throw(std::bad_alloc);    // Standard version
void * operator new (std::size_t, const std::nothrow_t &) throw(); // Placement version
void operator delete (void *) throw(); // Standard version
void operator delete (void *, const std::nothrow_t &) throw(); // Placement version

Note that the nothrow version of new is also part of the C++ standard, which is implemented through placement new and placement delete. The corresponding nothrow version of the new expression is as follows:

T *t = new(std::nothrow) T;

Constructing objects in user-defined space is the original intention of placement new, and it has also been included in the C++ standard as the default placement:

void * operator new (std::size_t, void * p) throw() { return p ; }
void operator delete (void *, void *) throw() { }

The corresponding placement new expression is used like this:

void *buffer = malloc(sizeof(ClassA));
ClassA *ptr = new(buffer)ClassA();

Placement new used in Memory Pool

In practical applications, memory pools are used the most, which is worth mentioning. Suppose a memory pool is defined as follows:

class Arena {
public:
    void* allocate(size_t);
    void deallocate(void*);
    // ...
};

By defining placement new and placement delete for the <span>Arena</span> class:

void* operator new(size_t sz, Arena& a)
{
    return a.allocate(sz);
}
void operator delete(void *ptr, Arena& a)
{
    return a.deallocate(ptr);
}

Creating an object:

Arena a();
X *p = new(a)X;

The current issue is how to <span>delete</span>, because we cannot call

delete p; 

This would only invoke the standard <span>operator delete()</span>, not the placement version for <span>Arena</span>. Therefore, we can only do this:

p->~X();
operator delete(p, a);

Or encapsulate the two operations into a template:

    template<class T> void destroy(T* p, Arena& a)
    {
        if (p) {
             p->~T();       // explicit destructor call
             a.deallocate(p);
        }
    }

Then call it like this:

destroy(p,a);

Leave a Comment