是否允许使用不同的类型替换`this`?

kin*_*nak 13 c++ language-lawyer

在这个问题的评论和答案中: 虚函数编译器优化c ++ 认为循环中的虚函数调用不能被虚拟化,因为虚函数可能this被另一个使用placement new的对象替换,例如:

void A::foo() { // virtual 
   static_assert(sizeof(A) == sizeof(Derived)); 
   new(this) Derived; 
}
Run Code Online (Sandbox Code Playgroud)

该示例来自LLVM博客关于虚拟化的文章

现在我的问题是:标准允许的是什么?

我可以在关于存储重用的cppreference上找到这个:(强调我的)

如果对象是可以破坏的,或者如果程序不依赖于析构函数的副作用,则不需要程序调用对象的析构函数来结束其生命周期.但是,如果一个程序结束了一个非平凡对象的生命周期,它必须确保在隐式调用析构函数之前就地构造一个相同类型的新对象(例如,通过放置new).

如果新对象必须具有相同的类型,则它必须具有相同的虚函数.因此,不可能有不同的虚函数,因此,虚拟化是可以接受的.

或者我误解了什么?

Joh*_*nck 5

你提供的报价说:

如果一个程序结束了一个非平凡对象的生命周期,它必须确保在隐式调用析构函数之前就地构造一个相同类型的新对象(例如,通过放置new)

本声明的意图涉及与您正在做的事情略有不同的事情.该语句意味着当你在不破坏其名称的情况下销毁一个对象时,仍然会引用具有原始类型的存储,o你需要在那里构造一个新对象,这样当隐式破坏发生时,就会有一个有效的对象摧毁.这是相关的,例如,如果你有一个自动("堆栈")变量,并且你调用它的析构函数 - 你需要在变量超出范围时调用析构函数之前在那里构造一个新实例.

声明作为一个整体,特别是它的"同一类型"子句与你正在讨论的主题无关,也就是说你是否可以构造一个具有相同存储要求的不同多态类型代替旧的.我不知道为什么你不应该被允许这样做.

现在,正如所说,你链接的问题是做一些不同的事情:它this在循环中使用隐式调用函数,问题是编译器是否可以假设vptr for this在该循环中不会改变.我相信编译器可以(并且clang -fstrict-vtable-pointers确实)假设这一点,因为this只有在放置后类型相同时才有效new.

因此,尽管您提供的标准中的引号与此问题无关,但最终结果是,在假设*this(或其vptr)的类型不能的情况下,优化器似乎可以对循环中进行的函数调用进行虚拟化.更改.存储在地址(及其vptr)中的对象的类型可以更改,但如果存在,则旧this的不再有效.


Ben*_*igt 4

看来您打算使用重新创建之前存在的句柄(指针、引用或原始变量名称)来使用新对象。仅当实例类型未更改,加上一些其他条件(不包括const对象和子对象)时才允许这样做:

[basic.life]

如果一个对象的生命周期结束后,在该对象占用的存储空间被重用或释放之前,在原对象占用的存储位置上创建一个新的对象,一个指向原对象的指针,一个指向该对象的引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可用于操作新对象,如果:

  • 新对象的存储完全覆盖原始对象占用的存储位置,

  • 新对象与原始对象具有相同的类型(忽略顶级cv-qualifiers),并且

  • 原始对象的类型不是 const 限定的,并且如果是类类型,则不包含任何类型为 const 限定的非静态数据成员或引用类型,并且

  • 原始对象是类型的最派生对象T,新对象是类型的最派生对象T(即,它们不是基类子对象)。

您对标准的引用只是这一标准的结果。

您提出的“去虚拟化反例”不满足这些要求,因此在替换对象后访问该对象的所有尝试都将导致未定义的行为。

该博客文章甚至在您查看的示例代码之后的下一句话中指出了这一点。