25

我刚读过

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

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

上第一个例子:

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

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

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

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

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

为什么让这个伪函数在形式语言语义上打一个洞是合理的,而不是根据代码是否像这些示例中那样将语义设置为它们需要的语义?

4

1 回答 1

14

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

因为编译器不能总是知道什么时候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 方式更改 的值。

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

其实是讨论过的。该论文提出了这些问题已经存在了多长时间(即:自 C++03 以来),并且经常采用此对象模型实现的优化。

该提案被拒绝,理由是它实际上无法解决问题。从这次旅行报告中

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

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

于 2021-02-12T20:01:40.263 回答