什么时候引用对象的动态类型会改变?

时间:2012-10-04 17:41:15

标签: c++ standards virtual

让我们从一个例子开始:

#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()

因此我在寻找:

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

2 个答案:

答案 0 :(得分:2)

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

对象的动态类型无法更改。即使在您的示例中,您也不是要更改对象的类型,而是在与旧对象相同的位置创建新的不同对象。如果您考虑一下,更改对象的动态类型将意味着就地调整对象的大小以容纳额外的数据成员并更改VMT(然后这将移动其他对象并搞砸指针。 ..)不能在语言规则范围内完成。

答案 1 :(得分:1)

这是undefined behaviour。您的magic示例违反了引用的语义。

此外,dynamic_cast适用于 down 广播。转为void*static_cast

明确回答您的问题:

  • 如果可以证明运行时类型,编译器可以“虚拟化”它喜欢的任何函数调用。
  • 如果引用超过它引用的对象,则为UB。
  • 您无法更改对象的动态类型,您可以做的最接近的事情是重新指定对象。

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

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