P0137引入了函数模板std::launder,并在关于联合、生命期和指针的部分对标准做了很多很多修改。
这篇论文要解决的问题是什么?我必须注意语言的哪些变化?我们在洗什么?
P0137引入了函数模板std::launder,并在关于联合、生命期和指针的部分对标准做了很多很多修改。
这篇论文要解决的问题是什么?我必须注意语言的哪些变化?我们在洗什么?
当前回答
我认为std:: laundry有两个目的。
持续折叠/传播的障碍,包括去虚拟化。 为基于细粒度对象结构的别名分析提供了障碍。
过度频繁的持续折叠/传播障碍(废弃)
从历史上看,c++标准允许编译器假设以某种方式获得的const限定的或引用的非静态数据成员的值是不可变的,即使它的包含对象是非const的,并且可以通过放置new来重用。
在c++ 17/P0137R1中,std:: laundry被引入作为禁用前面提到的(错误)优化(CWG 1776)的功能,这是std::optional所需要的。正如在P0532R0中所讨论的,std::vector和std::deque的可移植实现也可能需要std::launder,即使它们是c++ 98组件。
幸运的是,RU007(包括在P1971R0和c++ 20中)禁止这样的(错误的)优化。AFAIK没有编译器执行这个(错误)优化。
去虚拟化障碍
虚拟表指针(vptr)在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所需要的。鉴于vptr不是非静态数据成员,在某些情况下,编译器仍然允许基于vptr没有改变的假设(即,要么对象仍在其生命周期内,要么它被相同动态类型的新对象重用)执行去虚拟化。
对于一些用不同动态类型的新对象替换多态对象的不寻常用途(如图所示),需要std:: laundry作为去虚拟化的屏障。
IIUC Clang使用这些语义实现了std:: laundry (__builtin_laundry) (LLVM-D40218)。
基于对象结构的别名分析障碍
P0137R1还通过引入指针互转换改变了c++对象模型。IIUC这样的更改支持N4303中提出的一些“基于对象结构的别名分析”。
因此,P0137R1直接使用unsigned char [N]数组undefined中的reinterpret_cast指针解引用,即使该数组为另一个正确类型的对象提供了存储空间。然后需要std:: laundry来访问嵌套对象。
这种别名分析似乎过于激进,可能破坏许多有用的代码基。AFAIK它目前没有被任何编译器实现。
与基于类型的别名分析/严格别名的关系
IIUC std:: laundry和基于类型的别名分析/严格别名是不相关的。Std:: laundry要求正确类型的活对象位于所提供的地址。
然而,它们似乎意外地在Clang (LLVM-D47607)中被关联起来。
其他回答
Std: laundry这个名字很贴切,不过前提是你知道它是干什么用的。它执行内存清洗。
请看本文中的例子:
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的对象变量)永远不会改变。但我们只是改变了它。
(基本。life]/8说明了什么情况下可以通过对旧对象的变量/指针/引用访问新创建的对象。而拥有const成员是取消资格的因素之一。
所以…我们如何正确地谈论U.X.N ?
我们必须洗清我们的记忆:
assert(*std::launder(&u.x.n) == 2); //Will be true.
洗钱是用来防止人们追踪你的钱是从哪里来的。内存清洗用于防止编译器跟踪对象的来源,从而迫使它避免任何可能不再适用的优化。
另一个不合格的因素是如果你改变了对象的类型。Std: laundry在这里也有帮助:
alignas(int) char data[sizeof(int)];
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));
(基本。Life]/8告诉我们,如果在旧对象的存储中分配一个新对象,则不能通过指向旧对象的指针访问新对象。洗衣服可以让我们避开这个问题。
性病:“洗衣店”是一个错误的称呼。此函数执行与清洗相反的操作:它破坏指向内存,以消除编译器可能对指向值的任何期望。它排除了任何基于这种期望的编译器优化。
因此,在@NicolBolas的回答中,编译器可能会假设某些内存拥有某个常量值;或未初始化。你在告诉编译器:“那个地方(现在)被弄脏了,不要做那个假设”。
如果你想知道为什么编译器总是坚持它的天真的期望放在首位,并需要你明显地土壤的东西-你可能想要阅读下面的讨论:
为什么引入' std:: laundry '而不是让编译器来处理它?
... 这让我对std:: laundry产生了这样的看法。
我认为std:: laundry有两个目的。
持续折叠/传播的障碍,包括去虚拟化。 为基于细粒度对象结构的别名分析提供了障碍。
过度频繁的持续折叠/传播障碍(废弃)
从历史上看,c++标准允许编译器假设以某种方式获得的const限定的或引用的非静态数据成员的值是不可变的,即使它的包含对象是非const的,并且可以通过放置new来重用。
在c++ 17/P0137R1中,std:: laundry被引入作为禁用前面提到的(错误)优化(CWG 1776)的功能,这是std::optional所需要的。正如在P0532R0中所讨论的,std::vector和std::deque的可移植实现也可能需要std::launder,即使它们是c++ 98组件。
幸运的是,RU007(包括在P1971R0和c++ 20中)禁止这样的(错误的)优化。AFAIK没有编译器执行这个(错误)优化。
去虚拟化障碍
虚拟表指针(vptr)在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所需要的。鉴于vptr不是非静态数据成员,在某些情况下,编译器仍然允许基于vptr没有改变的假设(即,要么对象仍在其生命周期内,要么它被相同动态类型的新对象重用)执行去虚拟化。
对于一些用不同动态类型的新对象替换多态对象的不寻常用途(如图所示),需要std:: laundry作为去虚拟化的屏障。
IIUC Clang使用这些语义实现了std:: laundry (__builtin_laundry) (LLVM-D40218)。
基于对象结构的别名分析障碍
P0137R1还通过引入指针互转换改变了c++对象模型。IIUC这样的更改支持N4303中提出的一些“基于对象结构的别名分析”。
因此,P0137R1直接使用unsigned char [N]数组undefined中的reinterpret_cast指针解引用,即使该数组为另一个正确类型的对象提供了存储空间。然后需要std:: laundry来访问嵌套对象。
这种别名分析似乎过于激进,可能破坏许多有用的代码基。AFAIK它目前没有被任何编译器实现。
与基于类型的别名分析/严格别名的关系
IIUC std:: laundry和基于类型的别名分析/严格别名是不相关的。Std:: laundry要求正确类型的活对象位于所提供的地址。
然而,它们似乎意外地在Clang (LLVM-D47607)中被关联起来。