P0137引入了函数模板 std::launder
,并在有关联合、生命周期和指针的部分对标准进行了许多更改。
这篇论文要解决什么问题?我必须注意的语言变化是什么?我们在做什么launder
?
std::launder
恰当地命名,但前提是您知道它的用途。它执行内存清洗。
考虑论文中的示例:
struct X { const int n; };
union U { X x; float f; };
...
U u = {{ 1 }};
U
该语句执行聚合初始化,初始化with的第一个成员{1}
。
因为n
是一个const
变量,编译器可以自由假设它u.x.n
应该总是1。
那么如果我们这样做会发生什么:
X *p = new (&u.x) X {2};
因为X
是微不足道的,我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。新对象的n
成员将是 2。
所以告诉我......什么会u.x.n
返回?
显而易见的答案是 2。但这是错误的,因为允许编译器假设一个真正的const
变量(不仅仅是 a ,而是声明const&
的对象变量)永远不会改变。但我们只是改变了它。 const
[basic.life]/8说明了可以通过变量/指针/对旧对象的引用访问新创建的对象的情况。拥有const
会员是取消资格的因素之一。
u.x.n
那么......我们如何才能正确地谈论呢?
我们必须清洗我们的记忆:
assert(*std::launder(&u.x.n) == 2); //Will be true.
洗钱是用来防止人们追踪你的钱从哪里来的。内存清洗用于防止编译器跟踪您从何处获取对象,从而强制它避免任何可能不再适用的优化。
另一个不合格的因素是如果您更改对象的类型。std::launder
也可以在这里提供帮助:
aligned_storage<sizeof(int), alignof(int)>::type data;
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));
[basic.life]/8告诉我们,如果在旧对象的存储中分配新对象,则无法通过指向旧对象的指针访问新对象。launder
允许我们回避这一点。
std::launder
是误称。此函数执行与清洗相反的操作:它弄脏指向的内存,以消除编译器可能对指向的值的任何期望。它排除了基于这种期望的任何编译器优化。
因此,在@NicolBolas 的回答中,编译器可能会假设某些内存具有某个常量值;或未初始化。您是在告诉编译器:“那个地方(现在)被弄脏了,不要做那个假设”。
如果您想知道为什么编译器首先会始终坚持其幼稚的期望,并且需要您为它显着地弄脏东西-您可能想阅读以下讨论:
为什么要引入 `std::launder` 而不是让编译器处理它?
...这导致我对这种观点的理解是什么std::launder
。
我认为有两个目的std::launder
。
从历史上看,C++ 标准允许编译器假定以某种方式获得的 const 限定或引用非静态数据成员的值是不可变的,即使它的包含对象是非 const 并且可以通过放置 new 重用。
在 C++17/ P0137R1中,std::launder
作为禁用上述(错误)优化 ( CWG 1776 ) 的功能引入,这是std::optional
. 正如P0532R0中所讨论的,即使它们是 C++98 组件,也std::vector
可能std::deque
需要的可移植实现。std::launder
幸运的是, RU007(包含在P1971R0和 C++20中)禁止这种(错误)优化。AFAIK 没有编译器执行此(错误)优化。
虚拟表指针 (vptr) 在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所必需的。鉴于 vptr 不是非静态数据成员,编译器仍然允许基于 vptr 未更改的假设执行去虚拟化(即,对象仍然在其生命周期中,或者它被相同的动态类型)在某些情况下。
对于一些用不同动态类型的新对象(如图所示)替换多态对象的不寻常用途,std::launder
需要作为去虚拟化的障碍。
IIUC Clang使用这些语义 ( LLVM-D40218std::launder
) 实现( )。__builtin_launder
P0137R1还通过引入指针互转换性改变了 C++ 对象模型。IIUC 的这种变化使 N4303 中提出的一些“基于对象结构的别名分析”成为可能。
因此,P0137R1 直接使用从未定义的数组中解引用reinterpret_cast
'd 指针unsigned char [N]
,即使该数组正在为另一个正确类型的对象提供存储。然后std::launder
需要访问嵌套对象。
这种别名分析似乎过于激进,可能会破坏许多有用的代码库。AFAIK 目前没有任何编译器实现它。
IIUCstd::launder
和基于类型的别名分析/严格别名无关。std::launder
要求正确类型的活对象位于提供的地址。
但是,似乎它们在 Clang ( LLVM-D47607 ) 中意外关联。