是否重用存储开始新对象的生存期

Does reuse storage start lifetime of a new object?

本文关键字:对象 生存期 新对象 存储 开始 是否      更新时间:2023-10-16
#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()调用有 UB? new (this) D2;重用存储,但它也调用构造函数用于新对象的D2和启动生存期。在这种情况下,f()等于 this -> f() .也就是说,我们只是调用D2 f()成员函数。谁知道为什么是UB?

标准显示了这个例子 § 3.8 67 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 指向原始对象的指针,引用 到原始对象,否则原始对象的名称将 自动引用新对象,并且一旦生存期 新对象已启动,可用于操作新对象,如果:

— 新对象的存储完全覆盖存储位置 原始对象所占据的,并且 — 新对象是 与原始对象类型相同(忽略顶级 简历限定符(,以及

— 原始对象的类型不是 符合 const 条件,如果是类类型,则不包含任何非静态 类型为常量限定或引用类型的数据成员,以及

— 原始对象是 T 型的最派生对象 (1.8( 和 new 对象是 T 类型的最派生对象(即,它们不是 基类子对象(。

注意"和"字,上述条件都必须满足。

由于您不满足所有条件(您将派生对象放置在基类对象的内存空间中(,因此在引用隐式或显式使用此指针的内容时,存在未定义的行为

根据编译器的实现,这可能会或现在可能会失败,因为基类虚拟对象为 vtable 保留了一些空间,就地构造一个派生类型的对象,覆盖某些虚拟函数意味着 vtable 可能不同,放置对齐问题和其他低级内部,您将拥有一个简单的 sizeof 不足以确定您的代码是否正确。

这个结构非常有趣:

  • 放置新不能保证调用对象的析构函数。因此,此代码将无法正确确保对象的生命周期结束。

  • 因此,原则上您应该在重用对象之前调用析构函数。 但是,您将继续执行已死对象的成员函数。 根据标准节.9.3.1/2 如果为不属于 X 类型或派生自 X 类型的对象调用类 X 的非静态成员函数,则行为是未定义的。

  • 如果您没有像在代码中那样显式删除对象,则重新创建一个新对象(构造第二个 B 而不取消第一个 B,然后 D2 或这个新 B 的顶部(。

创建新对象完成后,当前对象的标识实际上在执行函数时已更改。 您无法确定指向将要调用的虚函数的指针是在放置新位置之前读取的(因此是指向 D1::f 的旧指针(还是之后(因此是 D2::f(。

顺便说一下,正是由于这个原因,在联合中你可以做什么或不能做什么有一些限制,其中相同的内存位置为不同的活动对象共享(参见标准中的第 9.5/2 点和第 9.5/4 点(。