C++多重继承下错误调用重写方法问题:为何调用methodA而非methodB?
methodB on a multi-inherited object via void* invoke methodA instead? 作为一个经常踩C多继承坑的老鸟,一眼就看出这是Java和C对象模型的核心差异导致的问题——你用Java的单继承思维写C++多继承代码,自然会踩这个指针偏移的坑。
问题根源:C++多继承的内存布局与指针偏移
在C++的多继承模型里,Child对象的内存布局中,BaseA和BaseB的子对象是分开放置的,它们的起始地址并不相同。举个简化的内存布局例子(不同编译器细节可能有差异,但核心逻辑一致):
+----------------+ | BaseA vtable | <- Child* 初始指向这里 | BaseA members | +----------------+ | BaseB vtable | <- BaseB* 应该指向的正确位置 | BaseB members | +----------------+ | Child members | +----------------+
当你把Child*转成void*传给callBaseB时,void*是没有类型信息的,编译器完全不知道它原本指向的是一个多继承对象。此时你直接把void*强制转成BaseB*,编译器没法自动调整指针的偏移量,结果这个BaseB*实际上指向了BaseA子对象的起始地址。
而虚函数调用依赖vtable:每个虚基类都有自己的vtable,BaseA的vtable第一个条目是methodA,BaseB的vtable第一个条目是methodB。现在你的b指针指向了BaseA的vtable区域,调用b->methodB(0)时,会去vtable的第一个位置找函数,自然就调用到了methodA,所以输出了Child A。
对比Java:Java是单继承模型,所有父类引用都指向同一个对象的起始地址,不存在多继承下的指针偏移问题,所以你在Java里这么写完全没问题,但C++的多继承玩的是另一套规则。
正确的解决办法
有两种靠谱的方式修复这个问题:
避免用
void*传递多继承对象
直接把callBaseB的参数改成BaseB*,编译器会自动处理指针偏移:void callBaseB(BaseB *p) { p->methodB(0); } int main() { auto child = new Child; callBaseB(child); // Child* 可隐式转为 BaseB*,编译器自动调整指针 delete child; // 别忘了释放内存 return 0; }必须用
void*时,先转回原类型再转基类
如果因为某些限制必须用void*,转换时要先转成Child*(保留类型信息),再转成BaseB*,让编译器帮你计算正确的偏移:void callBaseB(void *p) { BaseB *b = static_cast<BaseB*>(static_cast<Child*>(p)); b->methodB(0); }
内容的提问来源于stack exchange,提问作者desperateLord

