std:: launder
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Functions | ||||
|
(C++11)
|
||||
| Classes | ||||
|
(C++11)
|
||||
|
(C++17)
|
||||
| Types | ||||
| Objects | ||||
|
(C++20)
|
||||
| Object access | ||||
|
launder
(C++17)
|
|
Определено в заголовочном файле
<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
может сделать указатель на неактивный
член разыменовываемым в константном выражении |
запрещено |