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和我尝试过的一些在线编译器上普遍观察到了这种行为(这非常有力地表明我是错的)。
placement-new将派生类型的对象更新到基本类型的内存中。然后更新vptr以指向派生类型的vtable(在调试器中直接观察到)。
主要问题:这是预期的行为吗? (我想说“是”,如果不是,请向我解释)
我想相信,执行新的放置(假设派生类型对象有足够的内存)应该就位初始化派生类型的全新对象。
如果我的理解是正确的,则第一个b.talk()
应该输出"Duh"
,因为该对象现在是派生类型的。为什么仍打印"Be-e-e"
?
将派生类型的对象分配给基本类型的对象(除了引起对象拼接)不会复制vptr,因此,如果对象仍然是基本类型的对象,则可以预期第二个"Be-e-e"
输出当我们到达那行代码时输入。
为什么在~D()
分配中有一个b = D{};
呼叫?难道这不是一个临时文件,应该被复制删除,而无需对该临时文件进行析构函数调用吗?
最后一个使用指针的代码块“按预期”工作,就在这里进行健全性检查
答案 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
,并且您不能在D
和B
之类的不同类型之间删除副本。