通过此指针上的新位置重新初始化对象时未定义的行为

时间:2018-02-09 13:54:39

标签: c++ language-lawyer destructor placement-new

我在Piotr Padlewski的cppcon上看过一个演示文稿,说明以下是未定义的行为:

int test(Base* a){
  int sum = 0;
  sum += a->foo();
  sum += a->foo();
  return sum;
}

int Base::foo(){
  new (this) Derived;
  return 1;
}

注意:假设sizeof(Base) == sizeof(Derived)foo是虚拟的。

显然这很糟糕,但我很感兴趣为什么它是UB。我确实理解UB访问realloc ed指针,但他说,这是相同的。

相关问题:Is `new (this) MyClass();` undefined behaviour after directly calling the destructor?其中显示“如果没有例外,则确定” Is it valid to directly call a (virtual) destructor? new (this) MyClass();表示会导致UB。 (与上述问题相反)

C++ Is constructing object twice using placement new undefined behaviour?它说:

  

程序可以通过重用存储来结束任何对象的生命周期   对象占用的内容或通过显式调用析构函数   具有非平凡析构函数的类类型的对象。对于一个对象   对于具有非平凡析构函数的类类型,程序不是   需要在存储之前显式调用析构函数   对象占用被重用或释放;但是,如果没有   显式调用析构函数或者如果是delete-expression(5.3.5)   不习惯释放存储,析构函数不得   隐式调用以及任何依赖于副作用的程序   由析构函数生成的行为具有不确定的行为。

再次听起来没问题。

我在Placement new and assignment of class with const member

中找到了新的展示位置的另一个描述
  

如果在对象的生命周期结束之后和存储之前   对象占用的是重用或释放的,一个新的对象是   在原始对象占用的存储位置创建,a   指向原始对象的指针,引用的引用   到原始对象,或原始对象的名称   自动引用新对象,一旦生命周期   新对象已启动,可用于操作新对象,如果:

     
      
  • 新对象的存储空间恰好覆盖原始对象占用的存储位置,

  •   
  • 新对象与原始对象的类型相同(忽略顶级cv限定符),

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

  •   
  • 原始对象是类型为T的派生程度最高的对象,而新对象是类型为T的派生程度最高的对象(也就是说,它们不是   基类子对象)。

  •   

这似乎解释了UB。但这是真的吗?

这是不是意味着我没有std::vector<Base>?因为我认为由于其预分配std::vector必须依赖placement-new和明确的ctors。第4点要求它是Base显然不是最派生的类型。

2 个答案:

答案 0 :(得分:3)

我相信Elizabeth Barret Browning说得最好。让我数一下。

  1. 如果Base不是可以轻易破坏的,我们就无法清理资源。
  2. 如果sizeof(Derived)大于动态类型this的大小,我们就会破坏其他内存。
  3. 如果Base不是Derived的第一个子对象,则新对象的存储空间不会完全覆盖原始存储空间,您也会结束打破其他记忆。
  4. 如果Derived只是与初始动态类型不同的类型,即使它的大小与我们在{{3}上调用foo()的对象相同引用新对象。如果BaseDerived的任何成员具有const资格或是参考资料,情况也是如此。您需要std::launder任何外部指针/引用。
  5. 但是,如果sizeof(Base) == sizeof(Derived)Derived可以轻易破坏,BaseDerived的第一个子对象,您实际上只有Derived个对象。 .. 这可以。

答案 1 :(得分:2)

关于你的问题

  

...因为我假设由于它的预分配std :: vector必须依赖   安置新闻和明确的ctors。第4点要求它是   最基本的类型哪个Base显然不是。第4点需要它   是基本显然不是最基本的类型。

,我认为误解来自术语“最衍生对象”或“最衍生类型”:

类类型对象的“最派生类型”是实例化对象的类,无论该类是否具有其他子类。请考虑以下程序:

struct A {
    virtual void foo() { cout << "A" << endl; };
};

struct B : public A {
    virtual void foo() { cout << "B" << endl; };
};

struct C : public B {
    virtual void foo() { cout << "C" << endl; };
};

int main() {

    B b;  // b is-a B, but it also is-an A (referred  to as a base object of b).
          // The most derived class of b is, however, B, and not A and not C.
}

现在创建vector<B>时,此向量的元素将是类B的实例,因此元素的派生类型总是B,并且在你的情况下不是C(或Derived

希望这会带来一些启示。