当您在具有相同基类的派生类之间进行动态转换时会发生啥?

Posted

技术标签:

【中文标题】当您在具有相同基类的派生类之间进行动态转换时会发生啥?【英文标题】:What happens when you dynamic_cast between derived classes with the same base class?当您在具有相同基类的派生类之间进行动态转换时会发生什么? 【发布时间】:2018-05-30 11:12:51 【问题描述】:

我试图弄清楚当你 dynamic_cast 从一个派生类到另一个派生类时会发生什么。为什么下面的代码会引发分段错误?请注意,我并没有尝试将此代码用于任何事情。我只是想了解发生了什么。

同样值得注意的是,同样的代码使用static_cast。我似乎找不到任何文档来详细说明这里发生的事情。谁能解释一下?

struct base 
 
    virtual void printing()cout<<"base printing"<<endl;;
;
struct derived_1 :public base 
 
    virtual void printing()cout<<"derived_1 printing"<<endl;;
;
struct derived_2 :public base 
 
    virtual void printing()cout<<"derived_2 printing"<<endl;;
;

int main()

    base * b = new derived_2();
    derived_1 *d_1 = dynamic_cast<derived_1*>(b);

    // calling printing raises segmentation fault
    d_1->printing(); 

【问题讨论】:

注意:尽管合法,但您应该从子类中的重写方法中删除 virtual 说明符,并添加 override,如 void printing() override ... 【参考方案1】:

转换为derived_1 将失败,因为derived_2 base 对象,但不是 derived_1 对象。因此,您不能“侧向转换”到所需的指针类型。

并不是说每当dynamic_cast 失败时,它都会返回nullptr (except for reference types)。这最终会导致您的代码出现分段错误(通常,我建议您在使用动态转换的对象之前始终添加 if (d_1 != nullptr))。

更新:

顺便说一句,这实际上是dynamic_cast必要性的一个很好的例子。即使您可能想在示例中使用static_cast 并且它会编译,但您将处理未定义的行为。使用static_cast 编译时不会出现任何问题,但实际上您会使用损坏的类型。假设derived_1::printing() 访问了derived_1::a 中不存在的某个变量derived_2。通过将derived_2 对象(没有a)静态转换为derived_1 对象d_1,您会错误地假设d_1 包含一些有效的a,但事实并非如此.

例子:

// ...
struct derived_1 :public base

    const int a = 123;
    void printing() override cout<<"derived_1 printing " << endl;
    void foo()  cout << "Foo using constant = " << a << endl; 
;

// ...
int main()

    base * b = new derived_2();
    derived_1 *d_1 = static_cast<derived_1*>(b);
    d_1->printing(); // Will call derived_2::printing(), which is not what you expect!
    d_1->foo();      // Won't show a = 123 but some nonesense (mostly 0),
                     // because a is not defined in derived_2.

【讨论】:

嗯,有道理!谢谢您的帮助。我很困惑,因为我实际测试的代码是调用 2 个函数。所以它读起来像 d_1->non_virtual_member_function_of_derived_1(); d_1->打印();第一个函数调用成功了,所以我只是假设 d_1 不是 nullptr。但是,我现在看到 nullptr 实际上可以调用成员函数。尽管这是未定义的行为,但在这种情况下它是成功的 但是,我现在看到 nullptr 实际上可以调用成员函数。:不,你不能在 nullptr 上调用任何东西。 这就是我的想法,但显然你可以。这是未定义的行为,但你可以做到,它不会总是引发分段错误。这在此 SO link 中进行了讨论 您可以在空指针上调用非虚拟函数,尽管它被归类为未定义行为。然而,使用空指针调用虚函数将是致命的,因为需要知道对象的知识才能查找虚函数的实现——通常通过实例本身中的 VFT 指针。

以上是关于当您在具有相同基类的派生类之间进行动态转换时会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

详解C++中基类与派生类的转换以及虚基类

C#通过反射将派生类转换为基类异常

c++面向对象的程序设计

C++学习21 基类和派生类的赋值

C++:派生模板类和基类之间的向下转换和向上转换?

OOP1(定义基类和派生类)