P0137引入了函数模板std::launder
,并在有关联合、生存期和指针的部分对该标准进行了大量更改。
本文要解决的问题是什么?我需要注意的语言变化是什么?那我们在launder
什么呢?
发布于 2016-09-08 12:42:31
std::launder
的名字很贴切,但前提是您知道它的用途。它执行内存清洗。
考虑一下论文中的例子:
struct X { const int n; };
union U { X x; float f; };
...
U u = {{ 1 }};
该语句执行聚合初始化,使用{1}
初始化U
的第一个成员。
因为n
是一个const
变量,所以编译器可以自由地假设u.x.n
应该始终为1。
那么如果我们这样做会发生什么呢:
X *p = new (&u.x) X {2};
因为X
是微不足道的,所以我们不需要在创建新对象之前销毁旧对象,所以这是完全合法的代码。新对象的n
成员将为2。
告诉我..。u.x.n
将返回什么?
显而易见的答案将是2,但这是错误的,因为编译器被允许假设一个真正的const
变量(不仅仅是一个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
允许我们回避这一点。
发布于 2021-02-13 07:43:59
std::launder
是一个错误的提名者。此函数执行与清洗相反的操作:它污染指向的内存,以消除编译器对指向的值可能存在的任何期望。它排除了任何基于这样的期望的编译器优化。
因此,在@NicolBolas的答案中,编译器可能假设某些内存保存了一些常量值;或者是未初始化的。你在告诉编译器:“那个地方(现在)已经被污染了,不要做这个假设”。
如果你想知道为什么编译器一开始总是坚持它天真的期望,并且需要你明显地破坏它的东西-你可能想要阅读这篇讨论:
Why introduce std::launder
rather than have the compiler take care of it?
..。这就引出了我对std::launder
含义的看法。
https://stackoverflow.com/questions/39382501
复制相似问题