【发布时间】:2018-07-20 07:42:41
【问题描述】:
我在 cppcon 上看到 Piotr Padlewski 的演示文稿说以下是未定义的行为:
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 访问 realloced 指针,但他说,这是一样的。
相关问题: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? 它说:
程序可以通过重用存储来结束任何对象的生命周期 对象占用或显式调用析构函数 具有非平凡析构函数的类类型的对象。对于一个对象 具有非平凡析构函数的类类型,程序不是 需要在存储之前显式调用析构函数 占用的对象被重用或释放;但是,如果没有 显式调用析构函数,或者如果删除表达式 (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-news 和明确的ctors。第 4 点要求它是最衍生的类型,Base 显然不是。
【问题讨论】:
-
在
std::vector<Base>Base中绝对是最衍生的。不可能有实际上是Deriveds 的Bases。在您的new (this) Derived;中,this实际上是否有足够的内存来保存Derived? -
如果
Derived有额外成员(sizeof更大)会怎样?这应该让您了解当您执行此展示位置 new 时可能会发生什么 -
还要考虑
foo是一个虚方法。对foo()的第二次调用实际上会调用Derived::foo。 -
假设大小和虚拟 foo 相同。我现在补充了。我希望
Derived::foo被调用,但它是 UB
标签: c++ language-lawyer destructor placement-new