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