2

让我们从一个例子开始:

#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()上面的程序中去虚拟化吗?

因此,我正在寻找:

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

2 回答 2

3

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

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

于 2012-10-04T17:56:00.350 回答
0

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

此外,dynamic_cast用于向下转换。Cast tovoid*是一个static_cast.

要明确回答您的问题:

  • 如果编译器可以证明运行时类型,则编译器可以“去虚拟化”它喜欢的任何函数调用。
  • 如果一个引用比它引用的对象寿命长,它就是 UB。
  • 您无法更改对象的动态类型,您可以做的最接近的事情是重新分配指针。

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

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

于 2012-10-04T17:44:59.980 回答