Question

C++ Placement New: Uses, Object Lifetimes, and Safe Examples

cppmemory-managementnew-operatorplacement-new

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 new finds 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.

Take Quiz

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:

  1. slot reserves enough correctly aligned raw storage for one Counter. No Counter exists yet.
  2. new (slot) Counter(10) constructs a Counter in that storage. first->value is 10.
  3. first->~Counter() ends that object's lifetime. The byte array remains available.
  4. A second placement-new expression constructs a new Counter in the same storage.
  5. second is the pointer returned for the new object, whose value is 20.
  6. 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 for T but constructs T only when a value is present.
  • Variant-like storage: A type similar to std::variant holds 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 volatile register views and has platform-specific rules. Placement new is not the usual reason developers need it.
Take Quiz

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

TechniqueAllocates storage?Constructs an object?Who releases storage?
new T(args)YesYesdelete
new (address) T(args)NoYesThe owner of the supplied storage
std::construct_at(ptr, args...)NoYesThe owner of the supplied storage
malloc(sizeof(T))Yes, raw bytesNofree, after proper destruction if needed
std::make_unique<T>(args...)YesYesstd::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.

Take Quiz

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: use alignas(T) for local byte storage.
  • It starts a T object's lifetime in raw storage.
  • Store the returned pointer: T* object = new (storage) T(...);.
  • Do not call delete unless the storage itself came from a matching ordinary new expression.
  • End a class object's lifetime with std::destroy_at(object) or object->~T().
  • Reuse a slot only after destroying the object currently in it.
  • Prefer std::construct_at and std::destroy_at in 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.

Take Quiz

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

Take Quiz

Keep learning

Related questions

View more