派生类新的展示位置

时间:2018-10-17 03:09:15

标签: c++ polymorphism undefined-behavior placement-new vptr

C ++专家。需要这个小头刮刀的帮助:

#include <iostream>
struct B{
    virtual ~B() = default;
    virtual void talk() { std::cout << "Be-e-e\n"; }
};

struct D:B{
    void talk() override { std::cout << "Duh\n"; }
    ~D() { std::cout << "~D()\n"; }
};

int main(){
    B b{};      // vptr points to B
    new (&b) D; // vptr now points to D
    b.talk();   // "Be-e-e" (why? shouldn't the vptr be used?)
    b = D{};    // "~D()" (why? shouldn't the copying be elided?)
    b.talk();   // "Be-e-e"

    B*b1{new D};
    b1->talk(); // "Duh"
    delete b1;  // "~D()"

    return 0;
}

代码非常简单:在堆栈上有一个基础对象,在其中放置一个新的派生对象(是,eeew,但请耐心等待),并调用一个虚拟方法,期望派生的输出被打印出来。

实际输出

上面的代码产生以下输出:

Be-e-e
~D()
Be-e-e
Duh
~D()

在MSVC,gcc,clang和我尝试过的一些在线编译器上普遍观察到了这种行为(这非常有力地表明我是错的)。

第1部分

placement-new将派生类型的对象更新到基本类型的内存中。然后更新vptr以指向派生类型的vtable(在调试器中直接观察到)。

主要问题:这是预期的行为吗? (我想说“是”,如果不是,请向我解释)

我想相信,执行新的放置(假设派生类型对象有足够的内存)应该就位初始化派生类型的全新对象。


如果我的理解是正确的,则第一个b.talk()应该输出"Duh",因为该对象现在是派生类型的。为什么仍打印"Be-e-e"

将派生类型的对象分配给基本类型的对象(除了引起对象拼接)不会复制vptr,因此,如果对象仍然是基本类型的对象,则可以预期第二个"Be-e-e"输出当我们到达那行代码时输入。

第2部分

为什么在~D()分配中有一个b = D{};呼叫?难道这不是一个临时文件,应该被复制删除,而无需对该临时文件进行析构函数调用吗?

第3部分

最后一个使用指针的代码块“按预期”工作,就在这里进行健全性检查

1 个答案:

答案 0 :(得分:0)

查看代码:

B b{};      // vptr points to B
new (&b) D; // vptr now points to D

这是一个潜在的问题,原因有两个。首先,您没有为基础对象B调用 destructor 。其次,B的大小可能太小而无法容纳D类型的对象。

b.talk();   // "Be-e-e" (why? shouldn't the vptr be used?)

虚拟调用仅在通过指针或引用进行调用时起作用。这样的直接函数调用从不使用虚拟调度

b = D{};    // "~D()" (why? shouldn't the copying be elided?)

因为b被声明为类型B,并且您不能在DB之类的不同类型之间删除副本。