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

Posted

技术标签:

【中文标题】为啥要引入 `std::launder` 而不是让编译器处理它?【英文标题】:Why introduce `std::launder` rather than have the compiler take care of it?为什么要引入 `std::launder` 而不是让编译器处理它? 【发布时间】:2021-05-16 11:48:00 【问题描述】:

我刚刚读过

What is the purpose of std::launder?

坦率地说,我只能摸不着头脑。

让我们从@NicolBolas 接受的答案中的第二个示例开始:

aligned_storage<sizeof(int), alignof(int)>::type data; 
new(&data) int; 
int *p = std::launder(reinterpret_cast<int*>(&data)); 

[basic.life]/8 告诉我们,如果你在 旧对象的存储,您无法通过 指向旧的。 std::launder 允许我们回避这一点。

那么,为什么不直接更改语言标准,以便通过reinterpret_cast&lt;int*&gt;(&amp;data) 访问data 是有效/适当的?在现实生活中,洗钱是向法律隐瞒现实的一种方式。但我们没有什么可隐瞒的——我们在这里做的事情完全合法。那么,当编译器注意到我们正在以这种方式访问​​ data 时,为什么不能将其行为更改为 std::launder() 行为?

第一个例子:

X *p = new (&u.x) X 2;

因为 X 是微不足道的,我们之前不需要销毁旧对象 在它的位置创建一个新的,所以这是完全合法的代码。这 新对象的 n 成员将是 2。

那么告诉我...u.x.n 会返回什么?

显而易见的答案是 2。但那是错误的,因为编译器 允许假设一个真正的 const 变量(不仅仅是一个 const&, 但是声明为const) 的对象变量永远不会改变。但我们只是 改了。

那么为什么不让编译器不允许在我们写这种代码的时候做出这样的假设,通过指针访问常量字段呢?

为什么让这个伪函数在形式语言语义上打一个洞是合理的,而不是根据代码是否在这些示例中执行类似的操作来将语义设置为所需的内容?

【问题讨论】:

我猜launder 在很小的范围内禁用了优化。 "那么为什么不让编译器在我们编写这种代码时不允许做出假设呢?" 你的意思是,除了编译器会拥有的明显事实之外99% 的时间生成次优代码?大多数人在存储中重新创建对象,因此假设u.x.n 没有改变是完全合理的。 @dyp:你猜怎么着?它必须假设already. @einpoklum:“它必须已经假设了。”您将编译后的代码误认为是标准要求发生的事情。标准中没有任何内容明确要求编译器假设任何类型。 @NicolBolas:你是说这个例子只是一个错过的优化?我的印象是,函数值上的 const 限定符并不是对编译器的保证,而是让编译器阻止您(作者)更改它的安全预防措施。 【参考方案1】:

取决于代码是否在这些示例中执行类似操作

因为编译器无法始终知道何时“以这种方式”访问 data

就目前的情况而言,允许编译器假设以下代码:

struct foo int const x; ;

void some_func(foo*);

int bar() 
    foo f  123 ;
    some_func(&f);
    return f.x;

bar 将始终返回 123。编译器可能生成实际访问对象的代码。但是对象模型并不要求这个。 f.x 是一个const 对象(不是const 的引用/指针),因此无法更改。并且f 必须始终命名同一个对象(实际上,这些是您必须更改的标准部分)。所以f.x的值不能通过任何非UB的方式改变。

为什么有这个伪函数可以在形式语言语义上打个洞是合理的

这是actually discussed。那篇论文提出了这些问题已经存在了多久(即:自 C++03 以来),并且经常采用此对象模型实现的优化。

该提案被拒绝,理由是它实际上无法解决问题。来自this trip report:

然而,在讨论过程中发现,提议的替代方案无法处理所有受影响的场景(特别是 vtable 指针正在发挥作用的场景),并且没有获得共识。

该报告没有就此事进行任何具体细节,相关讨论也不公开。但是提案本身确实指出它不允许对 second 虚函数调用进行去虚拟化,因为第一次调用可能已经构建了一个新对象。所以即使是 P0532 也不会使launder 变得不必要,而只是减少了必要性。

【讨论】:

但是,如果在some_func() 内部,我在x 字段上使用const_cast 怎么办? 重新。您的第二条评论,缺少优化的编译器几乎不是怀疑标准的理由。错过优化的其他原因包括:没有人将这种情况添加到优化器中;以及期望非标准行为的用户群。 Similar case 没有结构得到优化;带有const 成员的结构并不常见 @einpoklum 在x 的生命周期内修改f.x(通过const_cast)是每个[dcl.type.cv]/4 的UB。在some_func 内结束x 和/或f 的生命周期是允许的,但这会导致bar 中的排序后访问成为[basic.life]/(8.3) 的UB。 @einpoklum:f.xconst。在其生命周期内无法对其进行修改。从它完全存活(例如,当任何f::f() 构造函数返回时)到它开始死亡(例如,当f::~f() 析构函数开始执行时),它是绝对不可变的。 我认为“const subobject”的例子不再相关:github.com/cplusplus/draft/commit/…

以上是关于为啥要引入 `std::launder` 而不是让编译器处理它?的主要内容,如果未能解决你的问题,请参考以下文章

std::launder 的效果是不是在调用它的表达式之后持续?

C++20 中的 std::launder 用例

我在哪里可以找到 std::launder 的真正作用? [复制]

如何解释 std::launder 的可达性要求?

std::launder 替代 pre c++17

带有就地多态容器的 std::launder