引用对象的动态类型何时可以更改

When may the dynamic type of a referred to object change?

本文关键字:何时可 类型 对象 动态 引用      更新时间:2023-10-16

让我们从一个例子开始:

#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中的实例后变得过时了。在这种情况下,用static_cast<B*>(s)->foo()代替b.foo()是否合法


因此,现在我们有了一个可能(或不)合法的例子,对于我们所有的标准化者来说,手头更普遍的问题是是否允许更改对象的动态类型。我们已经知道C++编译器可能会重用存储(幸运的是),所以这有点棘手。

这个问题可能看起来是理论性的,但它对编译器有直接的应用:编译器可能会在上面的程序中使b.foo()b.P::foo()失效吗?

因此,我正在寻找:

  • 关于我自己的小程序,这是一个明确的答案(我无法想出一个)
  • 一个改变对象动态类型的合法方法的可能例子(一个就足够了)

根据标准§8.5.3.2,初始化后引用不能绑定到另一个对象。由于放置new会创建一个新对象,因此您违反了该规则,并获得了未定义的行为。

对象的动态类型无法更改。即使在您的示例中,您也不是在更改对象的类型,而是在与旧对象相同的位置创建一个不同的新对象。如果你仔细想想,更改对象的动态类型意味着调整对象的大小以容纳额外的数据成员,并更改VMT(然后会移动其他对象并损坏指针…),这在语言规则范围内是无法做到的。

这是未定义的行为。您的magic示例违反了引用的语义。

此外,dynamic_cast用于向下铸造。铸造到void*的是static_cast

明确回答您的问题:

  • 如果编译器能够证明运行时类型,它可以"破坏"它喜欢的任何函数调用
  • 如果引用比它引用的对象寿命长,那么它就是UB
  • 您不能更改对象的动态类型,您能做的最接近的事情就是重新分配指针。

    Base * ptr;
    P p;
    N n;
    ptr = &p; ptr -> foo ();
    ptr = &n; ptr -> foo ();
    

但是pn是固定类型的,直到它们超出范围(或者,如果在堆上分配,当它们是deleted时)。