C++ 为什么调用错误的覆盖方法



我定义 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);
}

最新更新