Question
C++ Placement New: Uses, Object Lifetimes, and Safe Examples
Question
What practical uses does C++ placement new have? Have developers used it outside of memory-mapped hardware, and when is it appropriate to construct an object in memory that has already been allocated?
Short Answer
Placement new separates obtaining storage from constructing an object. By the end, you will know why allocators, memory pools, arena allocators, shared-memory systems, and in-place container implementations use it—and how to manage destruction and alignment safely.
Concept
Placement new constructs an object at an address you provide. Unlike ordinary new, it does not allocate memory.
Widget* widget = new Widget(42); // Allocates storage, then constructs Widget
Widget* same = new (buffer) Widget(42); // Constructs Widget in existing storage
The expression new (buffer) Widget(42) starts the lifetime of a Widget in the storage beginning at buffer. buffer must be large enough and correctly aligned for Widget.
This matters because storage and object lifetime are distinct ideas in C++:
- Storage is raw memory capable of holding bytes.
- An object exists only after construction has begun its lifetime.
- Destruction ends the object's lifetime, but usually does not release the underlying storage.
Placement new is not limited to memory-mapped hardware. It is a fundamental building block for custom allocators, object pools, arena allocation, and types that store another object directly inside their own memory.
In modern C++, prefer std::construct_at when possible. It expresses the same intent in a safer, library-friendly form.
Mental Model
Think of storage as an empty parking space and an object as a car.
- Ordinary
newfinds or creates a parking space and parks the car. - Placement new is used when you already chose the exact parking space and only need to park the car there.
- Calling the destructor removes the car, but the parking space still exists and can be reused.
You must not park a large car in a small space, and the space must be positioned correctly. In C++ terms, that means sufficient size and correct alignment.
Syntax and Examples
Include <new> for placement new.
#include <cstddef>
#include <iostream>
#include <new>
struct Connection {
explicit Connection(int id) : id(id) {
std::cout << "Opened connection " << id << '\n';
}
~Connection() {
std::cout << "Closed connection " << id << '\n';
}
int id;
};
int main() {
alignas(Connection) std::byte storage[sizeof(Connection)];
Connection* connection = new (storage) Connection(7);
std::cout << connection->id << '\n';
connection->~Connection();
}
storage is merely raw bytes at first. The placement-new expression constructs Connection(7) in those bytes and returns a pointer to the new object.
Notice the explicit destructor call. delete connection; would be wrong because the storage came from a local array, not ordinary new.
Modern equivalent:
#include <memory>
Connection* connection = std::construct_at(
reinterpret_cast<Connection*>(storage), 7);
std::destroy_at(connection);
std::construct_at is especially useful in generic code and standard-library-style implementations.
Step by Step Execution
Consider this reusable single-object slot:
#include <cstddef>
#include <new>
struct Counter {
explicit Counter(int value) : value(value) {}
~Counter() {}
int value;
};
int main() {
alignas(Counter) std::byte slot[sizeof(Counter)];
Counter* first = new (slot) Counter(10);
first->~Counter();
Counter* second = new (slot) Counter(20);
int result = second->value;
second->~Counter();
}
Execution steps:
slotreserves enough correctly aligned raw storage for oneCounter. NoCounterexists yet.new (slot) Counter(10)constructs aCounterin that storage.first->valueis10.first->~Counter()ends that object's lifetime. The byte array remains available.- A second placement-new expression constructs a new
Counterin the same storage. secondis the pointer returned for the new object, whose value is20.- The final explicit destructor call ends the second object's lifetime before the storage goes out of scope.
Always keep the pointer returned by the construction expression. When storage is reused, an old pointer may not be appropriate for referring to the replacement object in more complex lifetime situations.
Real World Use Cases
Placement new is useful whenever the program controls storage separately from construction.
- Memory pools: A game server can reserve a pool of fixed-size slots for frequently created request or entity objects. Construction occurs only when a slot is checked out.
- Arena allocators: A compiler, parser, or request handler can obtain raw blocks from an arena and construct many short-lived nodes in that arena.
- Containers and data structures: Dynamic arrays, linked lists, and hash tables allocate raw storage and construct elements one at a time. Standard library implementations rely on this principle.
- Optional in-place values: A type similar to
std::optional<T>reserves space forTbut constructsTonly when a value is present. - Variant-like storage: A type similar to
std::variantholds storage large enough for several alternatives and constructs only the currently selected alternative. - Shared memory or mapped files: Processes can map the same storage and, with careful design, construct compatible objects in it. This requires additional concerns such as synchronization and pointer portability.
- Memory-mapped hardware: It can be relevant, but direct hardware access often uses
volatileregister views and has platform-specific rules. Placement new is not the usual reason developers need it.
Real Codebase Usage
In production code, placement construction is often hidden inside a small abstraction rather than written repeatedly at call sites.
Allocator-backed construction
A container first allocates raw storage, then constructs an element only after validation succeeds:
#include <memory>
std::allocator<int> allocator;
int* memory = allocator.allocate(1);
try {
std::construct_at(memory, 42);
std::destroy_at(memory);
allocator.deallocate(memory, 1);
} catch (...) {
allocator.deallocate(memory, 1);
throw;
}
Guarding object lifetime
A class that owns in-place storage tracks whether an object currently exists. Its destructor destroys the object only when it was constructed.
if (has_value_) {
std::destroy_at(value_ptr());
}
Exception safety
If a constructor throws, no fully constructed object exists. Code that owns the raw storage must still release or recycle that storage. Resource-owning wrappers and standard containers handle these paths carefully.
For ordinary application code, prefer higher-level tools such as std::vector, std::optional, std::variant, smart pointers, and allocator-aware containers. Use direct placement construction when you are implementing such a tool or have a measured storage-management need.
Common Mistakes
Using delete on placement-constructed objects
alignas(int) std::byte storage[sizeof(int)];
int* number = new (storage) int(5);
delete number; // Wrong: storage was not allocated by ordinary new
Use the destructor when required. For int, no destruction is needed; for a class type, call std::destroy_at(number) or number->~Type().
Forgetting alignment
std::byte storage[sizeof(double)];
double* value = new (storage) double(3.14); // Not guaranteed correctly aligned
Fix it with alignas:
alignas(double) std::byte storage[sizeof(double)];
Providing storage that is too small
alignas(long long) std::byte storage[2];
new (storage) long long(9); // Undefined behavior
Use at least sizeof(Type) bytes.
Overwriting a live object
new (slot) Counter(1);
new (slot) Counter(2); // Wrong if the first Counter is still alive
Destroy the existing object before reusing its storage, unless you are working with a carefully specified special case.
Treating raw bytes as an object before construction
A cast does not construct an object:
Counter* counter = reinterpret_cast<Counter*>(slot);
counter->value = 1; // Wrong: no Counter lifetime has begun
Construct the object first with placement new or std::construct_at.
Forgetting cleanup when constructors throw
If storage came from an allocator or pool and construction throws, return or deallocate the raw storage. Use RAII wrappers where possible.
Comparisons
| Technique | Allocates storage? | Constructs an object? | Who releases storage? |
|---|---|---|---|
new T(args) | Yes | Yes | delete |
new (address) T(args) | No | Yes | The owner of the supplied storage |
std::construct_at(ptr, args...) | No | Yes | The owner of the supplied storage |
malloc(sizeof(T)) | Yes, raw bytes | No | free, after proper destruction if needed |
std::make_unique<T>(args...) | Yes | Yes | std::unique_ptr |
Placement new and std::construct_at both construct into existing storage. std::construct_at is generally the clearer modern spelling.
malloc only obtains raw storage; it does not run constructors. Mixing malloc with delete, or ordinary new with free, is invalid.
For everyday ownership, std::unique_ptr, std::vector, and std::optional are usually preferable because they manage lifetimes automatically.
Cheat Sheet
- Include
<new>to use placement-new syntax:new (address) T(args...). - Placement new does not allocate memory.
- The destination must have enough bytes: at least
sizeof(T). - The destination must be aligned for
T: usealignas(T)for local byte storage. - It starts a
Tobject's lifetime in raw storage. - Store the returned pointer:
T* object = new (storage) T(...);. - Do not call
deleteunless the storage itself came from a matching ordinarynewexpression. - End a class object's lifetime with
std::destroy_at(object)orobject->~T(). - Reuse a slot only after destroying the object currently in it.
- Prefer
std::construct_atandstd::destroy_atin modern C++. - Prefer RAII and standard-library containers unless you are implementing low-level storage management.
FAQ
Is placement new only for memory-mapped hardware?
No. Its most common uses are allocators, containers, memory pools, arena allocation, and in-place object storage.
Does placement new allocate memory?
No. It receives an address for storage that already exists and constructs an object there.
Do I need to call the destructor manually?
For non-trivial class types, yes, when you manage the storage manually. Use std::destroy_at(ptr). Trivially destructible types such as int need no destruction work.
Can I call delete after placement new?
Usually no. delete both destroys an object and attempts to deallocate its storage. Only use it when the allocation and deallocation strategy are designed to match.
Why is alignas(T) necessary?
Byte arrays are not automatically guaranteed to have the alignment required by every type. alignas(T) ensures the storage can safely hold T.
Should I use std::construct_at instead of placement new?
Usually, yes, in modern C++ code. It performs in-place construction with a clearer interface and works well in generic code. Placement new remains the underlying concept.
Can I construct another object in the same storage?
Yes, after ending the previous object's lifetime. Ensure the new type also fits and is correctly aligned for that storage.
Is placement new exception-safe?
The construction expression is safe, but you are responsible for the raw storage. If construction throws, no complete object exists, and allocated or pooled storage must still be cleaned up or returned.
Mini Project
Description
Build a small reusable Slot<T> type that can hold either zero or one object directly inside its own storage. This demonstrates the same lifetime-management idea used by optional-like types: reserve storage first, then construct and destroy a value only when needed.
Goal
Create a Slot<T> that constructs a value in place, provides access to it, and safely destroys it when cleared or when the slot is destroyed.
Requirements
Requirement 1
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.