315

P0137引入了函数模板 std::launder,并在有关联合、生命周期和指针的部分对标准进行了许多更改。

这篇论文要解决什么问题?我必须注意的语言变化是什么?我们在做什么launder

4

3 回答 3

321

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允许我们回避这一点。

于 2016-09-08T04:42:31.317 回答
6

std::launder是误称。此函数执行与清洗相反的操作:它弄脏指向的内存,以消除编译器可能对指向的值的任何期望。它排除了基于这种期望的任何编译器优化。

因此,在@NicolBolas 的回答中,编译器可能会假设某些内存具有某个常量值;或未初始化。您是在告诉编译器:“那个地方(现在)被弄脏了,不要做那个假设”。

如果您想知道为什么编译器首先会始终坚持其幼稚的期望,并且需要您为它显着地弄脏东西-您可能想阅读以下讨论:

为什么要引入 `std::launder` 而不是让编译器处理它?

...这导致我对这种观点的理解是什么std::launder

于 2021-02-12T23:43:59.037 回答
0

我认为有两个目的std::launder

  1. 一个障碍不断折叠/传播,包括去虚拟化。
  2. 细粒度的基于对象结构的别名分析的障碍。

过度激进的常数折叠/传播的障碍(被遗弃)

从历史上看,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 ) 中意外关联。

于 2021-12-20T08:31:26.050 回答