我定义 2 个类
class BaseA {
public:
virtual void methodA() = 0;
};
class BaseB {
public:
virtual void methodB(int val) = 0;
};
子继承 2 个基类
class Child : public BaseA, public BaseB {
public:
void methodA() override {
printf("Child An");
}
void methodB(int val) override {
printf("Child B %dn", val);
}
};
然后我编写以下代码。
void callBaseB(void *p) {
BaseB *b = (BaseB *) p;
b->methodB(0);
}
int main() {
auto child = new Child;
callBaseB(child);
return 0;
}
控制台打印Child A
为什么会这样?为什么不调用方法 B?
(这是当Java工程师尝试编写C++代码时发生的事情)
你应该这样做:void callBaseB(BaseB *p) {p->methodB(0);}
.
如果要将void *p
保留为参数,则需要先将其强制转换为正Child *
。也:
BaseB *b = (Child *)p;
b->methodB(0);
或:
Child *b = (Child *)p;
b->methodB(0);
或者,在转换为void *
之前转换为BaseB *
。然后从void *
转换回Base *
就可以了。
这里发生的情况是,BaseB
子对象在Child
内部处于非零偏移量。
将Child *
转换为BaseB *
(显式转换,或通过分配指针或调用指针上的BaseB
方法隐式)时,编译器会自动将指针偏移所需的量,以指向BaseB
子对象。
偏移量完全在编译时根据这两种类型确定(除非涉及虚拟继承)。
当你使用void *
模糊源类型时,编译器无法确定正确的偏移量,甚至不会尝试应用任何偏移量,所以你会得到奇怪的行为,因为生成的BaseB *
并不指向BaseB
实例,而是指向Child
内部的一些垃圾。
即使使用虚拟继承,尽管偏移量是在运行时确定的,但计算取决于已知的源指针类型。
无法将void*
转换为BaseB*
(如@PaulSanders所说),但您绝对可以按如下方式将Child*
转换为BaseB*
:
void callBaseB(Child* p)
{
BaseB* b = p;
b->methodB(0);
}
上面的代码应该成功调用methodB()
。
但是如果你真的需要使用void*
,你可以这样:
void callBaseB(void* p)
{
Child* b = (Child*)p;
b->methodB(0);
}