std::launder
| Определено в заголовке <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
может сделать указатель на неактивный
член разыменовываемым в константном выражении |
запрещено |