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 的存储空间内,或位于直接包含 z 作为元素的数组中,则这些字节可通过指向对象 y 的指针访问)。
那么
std
::
launder
(
p
)
将返回一个指向对象
x
的
T*
类型值。否则,其行为是未定义的。
如果
T
是函数类型或(可能带有 cv 限定符的)
void
,则程序非良构。
当且仅当其参数的(转换后)值可以替代函数调用时,
std::launder
可用于
核心常量表达式
。换言之,
std::launder
不会放宽常量求值中的限制条件。
注释
std::launder
对其参数没有影响。必须使用其返回值来访问对象。因此,丢弃返回值始终是错误的。
std::launder
的典型用途包括:
- 获取指向在同一类型现有对象存储中创建的对象的指针,其中指向旧对象的指针无法 被复用 (例如,因为任一对象是基类子对象);
-
通过指向为某对象提供存储空间的对象指针,获取由布局
new创建的对象的指针。
可达性
限制确保
std::launder
无法用于访问原始指针无法访问的字节,从而避免干扰编译器的逃逸分析。
int x[10]; auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // 正确 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])); // 正确 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(); // 正确 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; // 正确 const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // 正确 [](...){}(f, g, h); // 产生[[maybe_unused]]效果 }
缺陷报告
下列行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
| 缺陷报告 | 适用范围 | 发布时行为 | 正确行为 |
|---|---|---|---|
| LWG 2859 | C++17 |
可达
的定义未考虑来自指针可互转换对象的
指针算术运算 |
已包含 |
| LWG 3495 | C++17 |
std::launder
可能使指向非活跃成员的指针
在常量表达式中可解引用 |
已禁止 |