【问题标题】:In what situations / circumstances a dynamic_cast<> can fail?在什么情况下 dynamic_cast<> 会失败?
【发布时间】:2012-02-11 07:55:39
【问题描述】:

在修复庞大代码库中的错误时,我观察到一种奇怪的情况,引用的动态类型从原始Derived 类型更改为Base 类型!我提供了解释问题的最少代码:

struct Base {
  // some 'virtual' function
  protected: // copy constructor
  private:  // assignment operator
};

struct Derived : Base {
  ... // There are few more classes between `Base` and `Derived`
  ... // but for simplicity, I have put direct relation
};

void foo (Base &ref)
{
  SomeClass obj;
  obj.pVoid = &ref;  // pVoid is of void*

  // ----> typeid(ref) = Derived
  (*funcptr)(obj);
  // ----> typeid(ref) = Base !!!

  Derived *p = dynamic_cast<Derived*>(&ref);  // this fails ... i.e. "p = 0"
}

funcptr 是一个函数指针 (void (*)(SomeClass&amp;))。 funcptr 可以指向这么多函数,而且它们有自己的调用流程,所以很难调试。

很奇怪,在调用函数指针之后,ref的派生类型从Derived变成了Base。为了减轻我的工作,我怀疑从DerivedBase的对象切片,所以我将~Base()作为纯virtual并重新编译了整个源代码。但是没有编译错误,这意味着没有声明Base的对象。

refDerived 的动态类型更改为Base 并且稍后dynamic_cast 失败的潜在原因是什么?

【问题讨论】:

  • 如果 typeid(ref) 在调用之后是基数,那么函数就会与对象混淆。您正在传递一个指向对象的指针,因此很容易在整个对象上写随机的东西。
  • @LokiAstari,虽然这段代码已经稳定了好几年,但你所说的是我的主要嫌疑人。 pVoid 可能搞砸了,但我必须看看它是在什么情况下发生的。在此之前,我想检查一下是否遗漏了任何可能将动态类型更改为静态类型以供参考的琐碎事情。

标签: c++ dynamic-cast dynamic-typing


【解决方案1】:

我不相信上面的代码实际上是这样的,因为代码示例没有编译!您不能将由dynamic_cast&lt;Base*&gt;(&amp;ref) 生成的Base* 隐式转换为Derived*

也就是说,假设typeid() 的输出实际上是正确的,那么对于引用的类型 ID 更改有一些可行的解释。所有这些都表明程序中存在某种形式的错误:

  1. 被调用的函数会破坏对象,例如通过调用 dynamics_cast&lt;Base*&gt;(obj.pVoid)-&gt;~Base() 的道德等价物。
  2. 被调用的函数在obj.pVoid 指向的地址处使用放置new 构造一个新对象,例如:new(obj.pVoid) Base()
  3. 某些东西正在覆盖内存,导致在引用的位置留下一个 Base 对象。
  4. 可能还有更多原因...

就个人而言,我会赌第二种情况是 case,即在该位置构造一个对象。显然,没有看到被调用的函数是不可能的。

【讨论】:

  • 这是一个错字,我已经编辑到dynamic_cast&lt;Derived*&gt;(&amp;ref)。原因(2)可能是不可能的,因为原因,我已经在问题中提到过。我将Base::~Base() 设为纯virtual 并重新编译了代码,但没有编译器错误。这意味着没有 Base 类的对象创建。
【解决方案2】:

如果转换不明确,dynamic_cast(指向指针)可以返回0。举例说明:

class O {…};
class A : public virtual O {…};
class B : public A {…};
class C : public A {…};
class D : public B, public C {…};


void f(O& p) {
  A* const a(dynamic_cast<A*>(&p));
}

void g() {
  D d;
  f(d);
}

【讨论】:

  • 这种情况下没有多重继承。此外,即使它会在那里,在我的情况下它不会影响。因为在函数调用之前,Base&amp; ref的动态类型是Derived。所以它应该保持原样。
  • @iammilind 好的。我会留下它,以防它帮助别人。思考:也许您可以添加对参数的一个(或多个)虚拟对象的调用,并逐步查看实际执行的内容以帮助确定类型(或者可能暴露内存粉碎器?)。根据您拥有的程序,它可能会导致一些线索。降低可见性(或删除方法)有时也会有所帮助。
【解决方案3】:

在我的具体情况下,dynamic_cast&lt;&gt; 失败的原因是 delete 过早引用了引用!

一旦指针或引用被delete + 破坏,那么任何将其向下转换为原始类型的尝试都会导致进入“未定义行为”区域。在我的情况下,dynamic_cast&lt;&gt; 失败了。

void foo (Base &ref)
{
  SomeClass obj;
  obj.pVoid = &ref;

  (*funcptr)(obj);  // ----> delete (Base*)(obj.pVoid); in one of the callbacks

  Derived *p = dynamic_cast<Derived*>(&ref);  // fails => "p = 0"
}

【讨论】:

  • @paulm,是的,这个答案已经用粗体字提到了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-05
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多