让我们从一个例子开始:
#include <cstdio>
struct Base { virtual ~Base() {} virtual void foo() = 0; };
struct P: Base { virtual void foo() override { std::printf("Hello, World!"); } };
struct N: Base { virtual void foo() override {} };
void magic(Base& b);
// Example implementation that changes the dynamic type
// {
// void* s = dynamic_cast<void*>(&b);
// b.~B();
// new (s) N();
// }
int main() {
std::aligned_storage<sizeof(P), alignof(P)> storage;
void* s = static_cast<void*>(storage);
new (s) P();
Base& b = *static_cast<Base*>(s);
magic(b);
b.foo();
}
根据标准,应该b.foo()
打印什么?
个人意见:它是未定义的,因为b
在我们销毁magic
中的实例后变得陈旧。在这种情况下,将b.foo()
替换为static_cast<B*>(s)->foo()
使其合法吗?
所以现在我们有一个可能(或不合法)的例子,对于我们所有的标准主义者来说,更普遍的问题是是否允许改变对象的动态类型。我们已经知道C ++编译器可能会重用存储(幸运的是),因此它有点棘手。
这个问题可能看似理论化,但它可以立即应用于编译器:编译器是否可以在上面的程序中将b.foo()
虚拟化为b.P::foo()
?
因此我在寻找:
答案 0 :(得分:2)
根据标准的§8.5.3.2,引用在初始化后不能绑定到另一个对象。由于展示位置new
会创建一个新对象,因此您违反了该规则,并获得了未定义的行为。
对象的动态类型无法更改。即使在您的示例中,您也不是要更改对象的类型,而是在与旧对象相同的位置创建新的不同对象。如果您考虑一下,更改对象的动态类型将意味着就地调整对象的大小以容纳额外的数据成员并更改VMT(然后这将移动其他对象并搞砸指针。 ..)不能在语言规则范围内完成。
答案 1 :(得分:1)
这是undefined behaviour。您的magic
示例违反了引用的语义。
此外,dynamic_cast
适用于 down 广播。转为void*
是static_cast
。
明确回答您的问题:
您无法更改对象的动态类型,您可以做的最接近的事情是重新指定对象。
Base * ptr;
P p;
N n;
ptr = &p; ptr -> foo ();
ptr = &n; ptr -> foo ();
但是p
和n
是固定类型的,直到它们超出范围(或者,如果在堆上分配,它们是delete
d)。