在这个问题的评论和答案中:
Virtual function compiler optimization c++
有人认为循环中的虚函数调用不能被虚拟化,因为虚函数可能会使用placement new替换另一个对象this
,例如:
void A::foo() { // virtual
static_assert(sizeof(A) == sizeof(Derived));
new(this) Derived;
}
示例来自LLVM blog article about devirtualization
现在我的问题是:标准是否允许这样做?
我可以在cppreference上找到有关存储重用的信息:(强调我的)
如果对象是可以破坏的,或者程序不依赖于析构函数的副作用,则不需要程序调用对象的析构函数来结束其生命周期。但是,如果程序结束非平凡对象的生命周期,则必须确保在调用析构函数之前就地构造相同类型的新对象(例如,通过放置new)隐式
如果新对象必须具有相同的类型,则它必须具有相同的虚函数。因此,不可能有不同的虚函数,因此,虚拟化是可以接受的。
或者我误解了什么?
答案 0 :(得分:5)
您提供的报价说:
如果程序结束了一个非平凡对象的生命周期,它必须确保在隐式调用析构函数之前就地构造一个相同类型的新对象(例如,通过放置new)
本声明的意图涉及与您正在做的事情略有不同的事情。该语句意味着当你在不破坏其名称的情况下销毁一个对象时,仍然会引用具有原始类型的存储,o你需要在那里构造一个新对象,这样当隐式破坏发生时,就会有一个有效的对象摧毁。这是相关的,例如,如果你有一个自动(" stack")变量,并且你调用它的析构函数 - 你需要在变量超出范围时调用析构函数之前在那里构造一个新实例。
整个声明及其相同类型的声明"特别是,与您讨论的主题无关,这是否允许您构造具有相同存储要求的不同多态类型来代替旧的类型。我不知道为什么你不应该被允许这样做。
现在,正如所说的那样,你所链接的问题是做了一些不同的事情:它在一个循环中使用隐式this
调用一个函数,问题是编译器是否可以假设vptr为{{ 1}}不会在该循环中改变。我相信编译器可以(和this
确实)假设这一点,因为clang -fstrict-vtable-pointers
仅在展示位置this
后类型相同时才有效。
因此,虽然您提供的标准中的引号与此问题无关,但最终结果是,优化器似乎可以在假设{{1的类型]的假设下,对循环中进行的函数调用进行虚拟化。 (或其vptr)无法更改。存储在地址(及其vptr)中的对象类型可以更改,但如果存在,则旧new
不再有效。
答案 1 :(得分:4)
您打算使用在重新创建之前存在的句柄(指针,引用或原始变量名称)来使用新对象。仅当实例类型未更改时,才允许这样做,以及除const
个对象和子对象之外的其他一些条件:
来自[basic.life]
:
如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用原始对象的引用,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用于操作新对象,如果:
新对象的存储完全覆盖原始对象占用的存储位置
和
新对象与原始对象的类型相同(忽略顶级 cv-qualifiers ),
原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定或引用类型的非静态数据成员,并且
原始对象是类型
T
的派生程度最高的对象,新对象是类型T
的派生程度最高的对象(即,它们不是基类子对象)。 / p>
你对标准的引用仅仅是这个问题的结果。
您提出的"虚拟化反例"不符合这些要求,因此在替换对象后访问该对象的所有尝试都将导致未定义的行为。
博客文章甚至在你看过的示例代码之后的下一句中指出了这一点。