我在皮奥特帕德列斯基的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。我确实理解访问realloced指针的UB,但他说,这是一样的。
相关问题:在直接调用析构函数后的未定义行为?中,它说"ok,如果没有例外“,直接调用(虚拟)析构函数有效吗?说new (this) MyClass();会导致UB。(与上述问题相反)
程序可以通过重用对象占用的存储或显式调用具有非平凡析构函数的类类型对象的析构函数来结束任何对象的生存期。对于具有非平凡析构函数的类类型的对象,在对象占用的存储被重用或释放之前,程序不需要显式调用析构函数;但是,如果没有显式调用析构函数,或者如果未使用删除-表达式(5.3.5)释放存储,则不应隐式调用析构函数,并且任何依赖于析构函数产生的副作用的程序都具有未定义的行为。
听起来也没问题。
我在有成员的班级的布置和分配中找到了另一个新的位置描述
如果在对象的生存期结束后,在对象占用的存储被重用或释放之前,在原始对象占用的存储位置创建一个新对象,指向原始对象的指针,引用原始对象的引用,或原始对象的名称,则可以自动引用新对象,并且一旦新对象的生存期启动,就可以使用它来操作新对象,如果:
这似乎解释了UB。但真的是真的吗?
这不意味着,我不能拥有std::vector<Base>吗?因为我认为由于它的预分配,std::vector必须依赖于placement-new和显式的ctors。第4点要求它是派生最多的类型,而Base显然不是。
发布于 2018-02-09 16:10:02
我相信伊丽莎白·巴雷特·布朗宁说得最好。让我数一数。
Base不能被破坏,我们就无法清理资源。sizeof(Derived)大于动态类型的this的大小,我们将关闭其他内存。Base不是Derived的第一个子对象,那么新对象的存储将不完全覆盖原始存储,并且您还会破坏其他内存。Derived只是一个与初始动态类型不同的类型,即使它的大小与我们在不能使用上调用的引用新对象的对象相同。如果Base或Derived的任何成员都是const限定的或是引用,情况也是如此。您需要std::launder任何外部指针/引用。但是,如果sizeof(Base) == sizeof(Derived)和Derived是可销毁的,那么Base是Derived的第一个子对象,实际上只有Derived对象.这很好。
发布于 2018-02-09 14:42:22
关于你的问题
...Because我认为,由于它的预分配std::向量必须依赖于布局-新闻和显式的ctors。第4点要求它是基显然不是的最派生的类型,而第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)。
希望这能带来一些启示。
https://stackoverflow.com/questions/48707481
复制相似问题