派生的新位置 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
类型,您不能省略 D
和 B
等不同类型之间的副本。
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
类型,您不能省略 D
和 B
等不同类型之间的副本。