Placement syntax

Uses your system voice; voice and rate in settings.

Snapshot

≈ 10 min read

Names in other languages

No names in other languages are held for this entry yet.

Article bodies are English and are not machine-translated. A name in another language is not a translation of the article.

Placement syntax in the C++ programming language refers to the syntax for explicitly specifying the memory management of individual objects (i.e. their "placement" in memory). Normally, when an object is created dynamically, an allocation function is invoked in such a way that it will both allocate memory for the object, and initialize the object within the newly allocated memory. The placement syntax allows the programmer to supply additional arguments to the allocation function. A common use is to supply a pointer to a suitable region of storage where the object can be initialized, thus separating memory allocation from object construction. The "placement" versions of the new and delete operators and functions are known as placement new and placement delete . A new expression, placement or otherwise, calls a new function, also known as an allocator function, whose name is operator new . Similarly, a delete expression calls a delete function, also known as a deallocator function, whose name is operator delete . Any new expression that uses the placement syntax is a placement new expression, and any operator new or operator delete function that takes more than the mandatory first parameter ( size_t ) is a placement new or placement delete function. A placement new function takes two input parameters: size_t and void* .

Placement syntax in the C++ programming language refers to the syntax for explicitly specifying the memory management of individual objects (i.e. their "placement" in memory). Normally, when an object is created dynamically, an allocation function is invoked in such a way that it will both allocate memory for the object, and initialize the object within the newly allocated memory. The placement syntax allows the programmer to supply additional arguments to the allocation function. A common use is to supply a pointer to a suitable region of storage where the object can be initialized, thus separating memory allocation from object construction. The "placement" versions of the new and delete operators and functions are known as placement new and placement delete . A new expression, placement or otherwise, calls a new function, also known as an allocator function, whose name is operator new . Similarly, a delete expression calls a delete function, also known as a deallocator function, whose name is operator delete . Any new expression that uses the placement syntax is a placement new expression, and any operator new or operator delete function that takes more than the mandatory first parameter ( size_t ) is a placement new or placement delete function. A placement new function takes two input parameters: size_t and void* . void* operator new(size_t n); void* operator new[](size_t n); The C++ Standard Library provides two placement overloads each for these functions. Their declarations are: void* operator new(size_t n, const nothrow_t& t) noexcept; void* operator new(size_t n, void* p) noexcept; void* operator new[](size_t n, const nothrow_t& t) noexcept; void* operator new[](size_t n, void* p) noexcept; In all of the overloads, the first parameter to the operator new function is of type size_t , which when the function is called will be passed as an argument specifying the amount of memory, in bytes, to allocate. All of the functions must return type void* , which is a pointer to the storage that the function allocates. There are also placement delete functions. They are overloaded versions of the non-placement delete functions. The non-placement delete functions are declared as: void operator delete(void* p) noexcept; void operator delete[](void* p) noexcept; The standard library provides two placement overloads each for these functions. Their declarations are: void operator delete(void* p, const nothrow_t& t) noexcept; void operator delete(void* p, void* q) noexcept; void operator delete[](void* p, const nothrow_t& t) noexcept; void operator delete[](void* p, void* q) noexcept; In all of the overloads, the first parameter to the operator delete function is of type void* , which is the address of the storage to deallocate. Operators new and delete must reside in the global namespace and cannot have static linkage. void* operator new(size_t n, void* p) noexcept { return p; } void* operator new[](size_t n, void* p) noexcept { return p; } void operator delete(void* p, void* q) noexcept {} void operator delete[](void* p, void* q) noexcept {} There are various uses for default placement. Bjarne Stroustrup originally observed, in his book The Design and Evolution of C++, that pointer placement new is necessary for hardware that expects a certain object at a specific hardware address. It is also required for the construction of objects that need to reside in a certain memory area, such as an area that is shared between several processors of a multiprocessor computer. Other uses, however, include calling a constructor directly, something which the C++ language does not otherwise permit. The C++ language does allow a program to call a destructor directly, and, since it is not possible to destroy the object using a delete expression, that is how one destroys an object that was constructed via a pointer placement new expression, i.e. p->~X(); . void* operator new(size_t count, void* here) noexcept { return here; } void* operator new[](size_t count, void* here) noexcept { return here; } import std; using std::nothrow; struct X { // ... }; int main() { // Call the function operator new(size_t, const nothrow_t&) and (if successful) construct the object. X* p = new(nothrow) X; if (p) { // The storage has been allocated and the constructor called. delete p; } else { // An error has occurred. No storage has been allocated and no object constructed. } return 0; } import std; class Arena { public: // ... void* allocate(size_t n) { // Allocation method... } void deallocate(void* p) { // Deallocation method... } }; Note that arena allocation is provided in the C++ standard library by the std::pmr::monotonic_buffer_resource class. Similarly, define custom placement allocation and deallocation functions as follows: void* operator new(size_t size, Arena& arena) { return arena.allocate(size); } void operator delete(void* p, Arena& arena) { arena.deallocate(p); } The program would employ the placement syntax to allocate objects using different instances of the Arena class as follows: Arena arena1; Arena arena2; X* p1 = new(arena1) X; X* p2 = new(arena2) X; Destroying an object whose storage is allocated in such a fashion requires some care. Because there is no placement delete expression, one cannot use it to invoke the custom deallocator. One must either write a destruction function that invokes the custom deallocator, or call the placement delete function directly, as a function call. The former would resemble: void destroy(X* p, Arena& arena) { p->~X(); // First invoke the destructor explicitly. arena.deallocate(p); // Then call the deallocator function directly. } which would be invoked from a program as: Arena arena; X* p = new(arena) X; // ... destroy(p, arena); The latter would involve simply writing the destructor invocation and delete function call into the program: Arena arena; X* p = new(arena) X; // ... p->~X(); // First invoke the destructor explicitly. operator delete(p, arena); // Then call the deallocator function indirectly via operator delete(void*, Arena&). A common error is to attempt to use a delete expression to delete the object. This results in the wrong operator delete function being called. Dewhurst recommends two strategies for avoiding this error. The first is to ensure that any custom allocators rely upon the standard library's global, non-placement, operator new , and are thus nothing more than simple wrappers around the C++ library's memory management. The second is to create new and delete functions for individual classes, and customize memory management via class function members rather than by using the placement syntax. if defined(DEBUG_NEW) void* operator new(size_t size, const char* file, int line); void* operator new[](size_t size, const char* file, int line); void operator delete(void* p, const char* file, int line); void operator delete[](void* p, const char* file, int line); define New new(__FILE__, __LINE__) else define New new endif This would be employed in a program as follows: X* p = New X; The custom-written placement new functions would then handle using the supplied file and line number information in the event of an exception. For example: import std; using std::bad_alloc; using std::nothrow; class FailedAllocationException: public bad_alloc { private: const char* file; int line; public: FailedAllocationException(const char* file, int line): bad_alloc(), file{file}, line{line} {} }; void* operator new(size_t size, const char* file, int line) { if (void* p = ::operator new(size, nothrow)) { return p; } throw FailedAllocationException(file, line); } Since C++26, placement new may be used in contexts. import std; template class Box { private: alignas(T) unsigned char storage[sizeof(T)]; T* ptr = nullptr; constexpr void destroy() { if (active) { std::destroy_at(ptr); // manually calls the destructor ptr = nullptr; } } public: constexpr Box() = default; constexpr ~Box() { destroy(); } template constexpr T& emplace(Args&&... args) { destroy(); T* p = new(static_cast (storage)) T(std::forward (args)...); return *ptr; } constexpr T& get() { return *ptr; } }; import std; using std::bad_alloc; class Arena { // ... }; class X { public: X() { throw bad_alloc("Bad allocation"); } }; void* operator new(size_t n, const Arena& a) { std::println("Placement new called."); } void operator delete(void* p, const Arena& a) { std::println("Placement delete called."); } int main() { Arena arena; try { X* p = new(arena) X; } catch (const bad_alloc& e) { std::println("Exception caught: {}", e.what()); } return 0; } This is why the pointer placement delete functions are defined as no-operations by the standard library. Since the pointer placement new functions do not allocate any storage, there is no storage to be deallocated in the event of the object's constructor throwing an exception. If no matching placement delete function exists, no deallocation function is called in the event of an exception being thrown by a constructor within a placement new expression. There are also some (older) C++ implementations that do not support placement delete (which, like the exception-throwing allocator functions, were an addition made to C++ when it was standardized) at all. In both such situations, an exception being thrown by a constructor when allocating using a custom allocator will result in a memory leak. (In the case of the older C++ implementations, a memory leak will also occur with non-placement new expressions.)

Text derived from https://en.wikipedia.org/w/index.php?oldid=1358377631 under CC BY-SA 4.0; modifications have been made.