【问题标题】:When may the dynamic type of a referred to object change?被引用对象的动态类型何时可以改变?
【发布时间】:2012-10-04 17:41:14
【问题描述】:

让我们从一个例子开始:

#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&lt;B*&gt;(s)-&gt;foo() 是否合法?


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

这个问题似乎是理论上的,但它对编译器有直接的应用:编译器可以在上面的程序中将b.foo() 虚拟化为b.P::foo() 吗?

因此我正在寻找:

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

【问题讨论】:

  • 您正在将void* 的地址传递给展示位置new,您确定这是您的本意吗?
  • 至于问题,我确实知道引用不能重新绑定到不同的对象,这就是这样做的。所以它似乎是未定义的?
  • @BЈовић 我假设storagestatic_cast&lt;void*&gt;(s) 中被误写为s
  • @SethCarnegie:感谢您的两个错别字。至于未定义……我也这么认为,但我想得到一个明确的答案。

标签: c++ standards virtual


【解决方案1】:

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

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

【讨论】:

  • 这听起来很有希望。我一直在查找显式析构函数调用,据我所知,没有提到任何特殊规则。还在看……
  • 正如@spraff 指出的那样,实际上这与§3.8/7 相矛盾:[...] 指向原始对象的指针,指向原始对象的引用,否则原始对象的名称将自动引用新对象[...]。你一定喜欢标准辩论...(在其他 3.8./7 保证动态类型没有改变)
【解决方案2】:

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

另外,dynamic_cast 用于向下-casting。投射到void*static_cast

明确回答您的问题:

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

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

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

【讨论】:

  • 如果一个引用比它所引用的对象寿命更长,它就是 UB -> 我也这么认为,但我无法从 标准,我想知道b.~B() 是否会获得特定的传递,特别是。我知道要求这样的保证听起来很疯狂,但是……标准确实允许奇怪的事情。
  • 关于dynamic_cast的用法:这是dynamic_cast的一个特殊构造,它的作用是获取完整对象的地址,不管多么复杂它是(甚至通过虚拟继承)。
  • @MatthieuM。看我关于similar considerations的问题@
  • @spraff:啊!您引用的段落(3.8/7)似乎正是我想要的。它似乎并没有留下太多的回旋余地!在您的回答中可能值得引用。
猜你喜欢
  • 1970-01-01
  • 2016-03-22
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-05
相关资源
最近更新 更多