P0137引入了函数模板 std::launder,并在有关联合、生命周期和指针的部分对标准进行了许多更改。
std::launder
这篇论文要解决什么问题?我必须注意的语言变化是什么?我们在做什么launder?
launder
std::launder恰当地命名,但前提是您知道它的用途。它执行 内存清洗 。
考虑论文中的示例:
struct X { const int n; }; union U { X x; float f; }; ... U u = {{ 1 }};
U该语句执行聚合初始化,初始化with的第一个成员{1}。
U
{1}
因为n是一个const变量,编译器可以自由假设它u.x.n应该 总是 1。
n
const
u.x.n
那么如果我们这样做会发生什么:
X *p = new (&u.x) X {2};
因为X是微不足道的,我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。新对象的n成员将是 2。
X
所以告诉我......什么会u.x.n返回?
显而易见的答案是 2。但这是错误的,因为允许编译器假设一个真正的const变量(不仅仅是 a ,而是 声明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允许我们回避这一点。