使用派生类的新放置

placement new with derived class

本文关键字:新放 派生      更新时间:2023-10-16

C++ 大师。需要您帮助这个小挠头的人:

#include <iostream>
struct B{
virtual ~B() = default;
virtual void talk() { std::cout << "Be-e-en"; }
};
struct D:B{
void talk() override { std::cout << "Duhn"; }
~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,但请耐心等待(并调用虚拟方法,期望打印派生的输出。

实际输出

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

贝伊 ~D(( 贝伊 铜 ~D((

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

第 1 部分

放置新将派生类型对象重新新闻到基本类型内存中。这会更新 vptr 以指向派生类型的 vtable(直接在调试器中观察到(。

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

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


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

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

第 2 部分

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

第 3 部分

最后一个使用指针的代码块"按预期"工作,仅用于健全性检查

查看代码:

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?)

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

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

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