为何允许基类强制转换为派生类?此类转换输出结果解析
咱们先把问题捋清楚:明明基类对象不能直接转成派生类对象,但代码里通过C风格强制转换把A对象的地址转成了B*指针,调用pb->foo()输出了B::foo(),调用pb->fun()却输出了A::fun(),这两种不同的结果到底是怎么回事?
先看一下你提供的代码:
#pragma #include "pch.h" #include<iostream> using namespace std; class A { public: void foo() { printf("A::foo()\n"); } virtual void fun() { printf("A::fun()\n"); } }; class B : public A { public: void foo() { printf("B::foo()\n"); } void fun() { printf("B::fun()\n"); } }; int main(void) { A a; B *pb = (B*)&a; pb->foo(); pb->fun(); return 0; }
接下来咱们分两部分解释这两个函数的调用逻辑:
1. 为什么pb->foo()会调用B::foo()?
foo()是非虚成员函数,C++对非虚函数的调用采用静态绑定——也就是说,编译器在编译阶段就已经确定了要调用哪个函数,完全看指针/引用的声明类型,和指针实际指向的对象类型无关。
在代码里,pb的声明类型是B*,所以编译器在编译pb->foo()的时候,直接就把这个调用绑定到了B::foo(),哪怕pb实际指向的是一个A类型的对象,也不会影响这个绑定结果。
2. 为什么pb->fun()会调用A::fun()?
fun()是虚成员函数,C++对虚函数的调用采用动态绑定——运行阶段才会根据对象的实际类型来确定调用哪个函数。
这里的关键是:每个包含虚函数的类对象,内部都会有一个虚表指针(vptr),指向该类对应的虚表(vtable),虚表里存储了该类所有虚函数的地址。当你创建A类型的对象a时,它的vptr指向的是A类的虚表,而A类虚表里的fun对应的就是A::fun()的地址。
虽然你把&a强制转换成了B*,但对象a本身的内存结构并没有改变——它依然是A类型,vptr还是指向A的虚表。所以运行时调用pb->fun()的时候,会通过vptr找到A的虚表,最终调用A::fun()。
最后要提醒一句:这种把基类对象地址强制转换成派生类指针的行为是不安全的,属于未定义行为的范畴。如果B类有自己的成员变量,用这个指针去访问的话,很可能会导致内存越界、程序崩溃等问题,实际开发中千万不要这么写。
内容的提问来源于stack exchange,提问作者AndyLi

