Low-Level Memory

Transparent Replacement and std::launder

When the lifetime of an object expires and a new object of a compatible type is constructed in exactly the same storage, then that new object may transparently replace the old. Under certain circumstances, a pointer that pointed to the old object still points to the new one. When those circumstances do not apply, then the original pointer becomes invalid, and we must use std::launder to obtain a valid pointer to the new object.

The rule for transparent replacement is that the new object must occupy the same bytes as the old, and its type must be the same, or have a sufficient relationship with the type of the deleted object. Normally, that condition will be achieved by destroying one object and then constructing another one in the same buffer.

An example of the first case where the pointer remains valid after reconstruction:

#include <iostream>
#include <memory>

int main()
{
    unsigned char buffer[sizeof(int)];
    // Reinterpret the value as the requested type
    int* p{std::construct_at(reinterpret_cast<int*>(buffer), 10)};
    std::cout << *p << '\n';
    std::destroy_at(p);
    p = std::construct_at(reinterpret_cast<int*>(buffer), 20);
    std::cout << *p << '\n';
}

The first call to std::construct_at produces a new int with the value 10 in the buffer and returns a pointer that is stored in p. Printing *p shows 10. std::destroy_at terminates the lifetime of that int. The second std::construct_at creates a new int with a value of 20 in the same bytes and again stores the result in p. Because the new object transparently replaces the old, p is still valid and the second print will show 20.

When a const member is present, the following will occur.

#include <iostream>
#include <memory>
#include <new>

struct S
{
    const int value;

    S(int v) : value{v}
    {
    }
};

int main()
{
    // Query the size in bytes without evaluating the operand
    unsigned char buffer[sizeof(S)];
    S* p{std::construct_at(reinterpret_cast<S*>(buffer), 5)};
    std::cout << p->value << '\n';
    std::destroy_at(p);
    std::construct_at(reinterpret_cast<S*>(buffer), 7);
    p = std::launder(p);
    std::cout << p->value << '\n';
}

The first call to std::construct_at constructs a new S object with the value of its member variable at 5, and it stores the pointer to the new object in p. Printing p->value yields 5. When std::destroy_at has executed, that object's lifetime has terminated.

The second call to std::construct_at constructs a new S with the value 7 in the same location.

Due to the fact that S has a const member, the compiler has no guarantee that p is still pointing to the new object. std::launder(p) provides a valid pointer to the new object, which is assigned back to p. The second print correctly shows 7.

Any reference or pointer use of the object that formerly resided in the buffer without std::launder would result in undefined behavior. The compiler is allowed to assume that no change was ever made to that const member of that object, and thus may even regenerate the old value of that member for output.

std::launder is defined in the <new> header. It simply takes a pointer and returns a pointer of the same type that points at the object at the specified address, assuming transparent replacement has just occurred. It does not modify the bits in any way; it only serves to tell the compiler about the transparency.

This gives us a way to refer to an object which has just been placed into storage, and provides a means to keep using our existing pointers when the language rules permit. If the language rules do not permit use of the existing pointer, std::launder must be employed.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.