派生的新位置 class

placement new with derived class

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将派生类型的对象重新new到基类型的内存中。然后更新 vptr 以指向派生类型的 vtable(直接在调试器中观察到)。

主要问题:这是预期的行为吗? (我想说 "yes" 所以如果不是 - 请向我解释)

我相信执行新放置(前提是派生类型对象有足够的内存)应该就地初始化派生类型的全新对象。


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

将派生类型对象分配给基类型对象(除了导致对象拼接)不会复制 vptr,所以第二个 "Be-e-e" 输出是预期的,前提是对象仍然是当我们到达该行代码时的基本类型。

第 2 部分

为什么在 b = D{}; 作业中有一个 ~D() 调用?它不是一个应该复制省略的临时文件,不需要对该临时文件调用析构函数吗?

第 3 部分

最后一段使用指针的代码有效"as expected",这里仅用于完整性检查

查看代码:

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

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

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

虚拟调用仅在通过指针或引用调用时有效。像这样的直接函数调用从不使用 virtual dispatch.

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

因为 b 被声明为 B 类型,您不能省略 DB 等不同类型之间的副本。