Namespaces
Variants

std:: launder

From cppreference.net
Utilities library
Memory management library
( exposition only* )
Allocators
Uninitialized memory algorithms
Constrained uninitialized memory algorithms
Memory resources
Uninitialized storage (until C++20)
( until C++20* )
( until C++20* )
( until C++20* )

Garbage collector support (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
Определено в заголовочном файле <new>
template < class T >
constexpr T * launder ( T * p ) noexcept ;
(начиная с C++17)

Девиртуализационный барьер относительно p . Возвращает указатель на объект по тому же адресу, который p представляет, при этом объект может быть новым подобъектом базового класса, наиболее производный класс которого отличается от исходного объекта * p .

Формально, при заданных

  • указатель p представляет адрес A байта в памяти
  • объект x расположен по адресу A
  • x находится в пределах своего времени жизни
  • тип x совпадает с T , игнорируя cv-квалификаторы на каждом уровне
  • каждый байт, который был бы доступен через результат, доступен через p (байты доступны через указатель, который указывает на объект y , если эти байты находятся в пределах хранилища объекта z , который является указательно-конвертируемым с y , или в пределах непосредственно включающего массива, элементом которого является z ).

Тогда std :: launder ( p ) возвращает значение типа T* , указывающее на объект x . В противном случае поведение не определено.

Программа является некорректной, если T является типом функции или (возможно cv-квалифицированным) void .

std::launder может использоваться в константном выражении ядра тогда и только тогда, когда (преобразованное) значение его аргумента может быть использовано вместо вызова функции. Другими словами, std::launder не ослабляет ограничения при константной оценке.

Примечания

std::launder не оказывает никакого эффекта на свой аргумент. Для доступа к объекту необходимо использовать возвращаемое значение. Таким образом, отбрасывание возвращаемого значения всегда является ошибкой.

Типичные применения std::launder включают:

  • Получение указателя на объект, созданный в области памяти существующего объекта того же типа, когда указатели на старый объект не могут быть повторно использованы (например, когда любой из объектов является подобъектом базового класса);
  • Получение указателя на объект, созданный через placement new , из указателя на объект, предоставляющий память для этого объекта.

Ограничение reachability гарантирует, что std::launder не может быть использован для доступа к байтам, недоступным через исходный указатель, тем самым вмешиваясь в escape-анализ компилятора.

int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // OK
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
// Неопределённое поведение: x2[1] был бы достижим через результирующий указатель на x2[0]
// но не достижим из исходного объекта
struct X { int a[10]; } x3, x4[2]; // стандартная компоновка; предполагаем отсутствие заполнения
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK
auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));
// Неопределённое поведение: x4[1] был бы достижим через результирующий указатель на x4[0].a
// (который является указательно-взаимопреобразуемым с x4[0]) но не достижим из исходного объекта
struct Y { int a[10]; double y; } x5;
auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0]));
// Неопределённое поведение: x5.y был бы достижим через результирующий указатель на x5.a
// но не достижим из исходного объекта

Пример

#include <cassert>
#include <cstddef>
#include <new>
struct Base
{
    virtual int transmogrify();
};
struct Derived : Base
{
    int transmogrify() override
    {
        new(this) Base;
        return 2;
    }
};
int Base::transmogrify()
{
    new(this) Derived;
    return 1;
}
static_assert(sizeof(Derived) == sizeof(Base));
int main()
{
    // Случай 1: новый объект не может быть прозрачно заменяемым, потому что
    // он является базовым подобъектом, а старый объект является полным объектом.
    Base base;
    int n = base.transmogrify();
    // int m = base.transmogrify(); // неопределенное поведение
    int m = std::launder(&base)->transmogrify(); // OK
    assert(m + n == 3);
    // Случай 2: доступ к новому объекту, хранилище которого предоставлено
    // массивом байтов, через указатель на массив.
    struct Y { int z; };
    alignas(Y) std::byte s[sizeof(Y)];
    Y* q = new(&s) Y{2};
    const int f = reinterpret_cast<Y*>(&s)->z; // Доступ к члену класса является неопределенным
                                               // поведением: reinterpret_cast<Y*>(&s)
                                               // имеет значение "указатель на s" и не
                                               // указывает на объект Y
    const int g = q->z; // OK
    const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK
    [](...){}(f, g, h); // создает эффект [[maybe_unused]]
}

Отчеты о дефектах

Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Applied to Behavior as published Correct behavior
LWG 2859 C++17 определение reachable не учитывало арифметику указателей
от указательно-интерконвертируемого объекта
включено
LWG 3495 C++17 std::launder может сделать указатель на неактивный
член разыменовываемым в константном выражении
запрещено