dynamic_cast<>在什么情况下/情况下可能会失败?

In what situations / circumstances a dynamic_cast<> can fail?

本文关键字:情况下 失败 在什么 cast lt gt dynamic      更新时间:2023-10-16

在修复庞大代码库中的一个错误时,我观察到一个奇怪的情况,引用的动态类型从原始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&))。funcptr可以指向很多函数,而且它们有自己的调用流,因此很难调试。

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

ref Derived的动态类型更改为Basedynamic_cast稍后发生故障的潜在原因是什么?

我不相信上面的代码是真实的,因为代码示例没有编译!不能将dynamic_cast<Base*>(&ref)生成的Base*隐式转换为Derived*

也就是说,假设typeid()的输出实际上是正确的,则对参考的类型ID的变化有一些可行的解释。所有这些都表明程序中存在某种形式的错误:

  1. 被调用的函数破坏对象,例如通过调用dynamics_cast<Base*>(obj.pVoid)->~Base()的道德等价物
  2. 被调用的函数使用布局newobj.pVoid指向的地址处构造一个新对象,即类似于new(obj.pVoid) Base()的对象
  3. 某些内容正在重写内存,导致在引用的位置留下Base对象
  4. 可能还有更多的原因

就我个人而言,我会把赌注押在第二种情况上,即一个对象被构造到该位置。显然,如果没有看到被调用的函数,就无法判断。

如果转换不明确,

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);
}

在我的特定情况下,dynamic_cast<>失败的原因是由于delete过早地引用!

一旦指针或引用被delete+破坏,那么任何将其向下转换为原始类型的尝试都将导致进入"未定义行为"区域。在我的情况下,dynamic_cast<>出现故障。

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"
}