重用存储是否会启动新对象的生命周期?

时间:2014-09-04 04:43:56

标签: c++ class inheritance struct

#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?

2 个答案:

答案 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不足以确定你的代码是否正确。