std::launder
| Определено в заголовочном файле <new>
|
||
template< class T > constexpr T* launder( T* p ) noexcept; |
(начиная с C++17) (до C++20) |
|
template< class T > [[nodiscard]] constexpr T* launder( T* p ) noexcept; |
(начиная с C++20) | |
Provenance fence по отношению к 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 включают:
- Получение указателя на объект, созданный в хранилище существующего объекта того же типа, где указатели на старый объект нельзя использовать повторно (например, потому что каждый объект является подобъектом базового класса);
- Получение указателя на объект, созданный размещающим
newиз указателя на объект, предоставляющий хранилище для этого объекта.
Ограничение достижимости гарантирует, что 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++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| LWG 2859 | C++17 | определение достижимости не учитывало арифметику указателя из взаимопреобразуемого по указателю объекта |
включено |
| LWG 3495 | C++17 | std::launder может сделать указатель на неактивный элементразыменовываемым в константном выражении |
запрещено |