#include <cstdlib>
struct B {
virtual void f();
void mutate();
virtual ~B();
};
struct D1 : B { void f(); };
struct D2 : B { void f(); };
void B::mutate() {
new (this) D2; // reuses storage — ends the lifetime of *this
f(); // undefined behavior - WHY????
... = this; // OK, this points to valid memory
}
我需要解释为什么f()
invokation有UB? new (this) D2;
重用存储,但它也调用D2
的构造函数,并从此开始生成新对象的生命周期。在这种情况下,f()
等于this -> f()
。 我们只需拨打f()
的{{1}}成员函数。谁知道为什么是UB?
答案 0 :(得分:1)
这个结构非常有趣:
不保证placement-new可以调用对象的析构函数。因此,此代码无法正确确保对象的使用寿命。
所以原则上你应该在重用对象之前调用析构函数。但是你会继续执行一个已死的对象的成员函数。根据标准section.9.3.1 / 2 如果为非X类型的对象或从X派生的类型调用类X的非静态成员函数,则行为未定义。< / EM>
如果你没有像在代码中那样明确地删除你的对象,那么你就会重新创建一个新对象(构建第二个B而不会让第一个对象失败,然后D2或者这个新B的顶部)。
完成新对象的创建后,当前对象的标识实际上在执行函数时已更改。您无法确定是否在您的placement-new(因此指向D1 :: f的旧指针)之后或之后(因此D2 :: f)读取了将要调用的虚函数的指针。
顺便说一句,正是由于这个原因,对于你在联合中可以做什么或不能做什么有一些限制,其中为不同的活动对象共享相同的内存位置(参见第9.5 / 2点,特别是标准中的9.5 / 4点)。
答案 1 :(得分:1)
标准显示了这个例子§3.867 N3690:
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type C
请注意,此示例在就地构造新对象之前终止对象的生命周期(与不调用析构函数的代码进行比较)。
但即使你这样做,标准也会说:
如果在对象的生命周期结束之后和存储之前 对象占用的是重用或释放的,一个新的对象是 在原始对象占用的存储位置创建,a 指向原始对象的指针,引用的引用 到原始对象,或原始对象的名称 自动引用新对象,一旦生命周期 新对象已启动,可用于操作新对象,如果:
- 新对象的存储完全覆盖存储位置 原始对象占用了哪个, - 新对象是 与原始对象相同的类型(忽略顶级 cv-qualifiers)和
- 原始对象的类型不是 const-qualified,如果是类类型,则不包含任何非静态 类型为const限定的数据成员或引用类型,
- 原始对象是类型为T的最派生对象(1.8) new对象是T类型的最派生对象(也就是说,它们不是 基类子对象)。
注意'和'字样,必须满足上述条件。
由于您没有满足所有条件(您将派生对象置于基类对象的内存空间中),因此在引用具有隐式或者隐式的内容时,您具有未定义行为明确使用这个指针。
由于基类虚拟对象为 vtable 保留了一些空间,就地构建了一个派生类型的对象,它可能会或者现在可能会打击,这取决于编译器的实现。虚函数意味着vtable可能不同,放置对齐问题和其他低级内部,你会有一个简单的sizeof不足以确定你的代码是否正确。