cpp继承多接口,调用虚函数跳转到错误接口的虚函数的奇怪问题

⌚Time: 2023-09-03 17:07:39

👨‍💻Author: Jack Ge

问题重现

定义了两个接口IA IB


class IA{

public:

    virtual void funA() = 0;

};

class IB{

public:

    virtual void funB() = 0;

};

使用一个类继承了这两个接口,实现了他们的纯虚函数


class CC:public IA,public IB{

public:

    void funA(){

        cout<<"IA func"<<endl;

    }

    void funB(){

        cout<<"IB func"<<endl;

    }

};

定义一个start函数,参数为void*,利用虚函数的多态特性,在函数内通过类型转换分别调用接口IA和IB的虚函数


void start(void *arg){



    IA *ia = (IA*)arg;

    ia->funA();

    IB *ib = (IB*)arg;

    ib->funB();

}

在主函数里,定义一个子类,传入start函数,期望的是输出接口IA和IB各自的虚函数




int main(){

    CC *cc = new CC();

    start(cc);

    return 0;

}

实际的输出却全部调用了IA的函数funA


g++ a.cpp

a

IA func

IA func

问题分析

多继承的情况下,子类将有多个虚函数表,start函数内,ia指向了接口IA的虚函数表,而ib也指向了IA的虚函数表,导致ib调用了IA的虚函数。IA是子类第一个继承的接口,也就是说他们都指向了第一个继承的父类的虚函数表

如果为程序再添加函数




void startA(IA *arg){



    arg->funA();

}

void startB(IB *arg){



    arg->funB();

}

main函数改为


int main(){

    CC *cc = new CC();

    startA(cc);

    startB(cc);

    return 0;

}

此时就会出现想要的结果,成功的调用了IA和IB接口内的虚函数


g++ a.cpp

a

IA func

IB func

尝试下面的代码


int main(){

    void *cc = new CC();

    IB *ib = (IB*)cc;

    ib->funB();

    IB *ibb = (CC*)cc;

    ibb->funB();

    

    return 0;

}

输出


g++ a.cpp

a

IA func

IB func

可以看到第一个IB指针调用了接口IA的函数,而第二个IB指针是正确的调用了它的函数

当子类为多继承接口,并且指针类型为void*时,进行强制类型转换为父类,就会出现虚函数表指向的错误,指向第一个继承的接口类的虚函数表,只有知道子类的类型再进行转换为父类,才可以指向正确的虚函数表。

解决办法

解决办法是使用dynamic_cast进行类型转换。dynamic_cast是将一个基类对象指针(或引用)转换到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。

将子类指针先转换成继承的第一个接口类的指针,然后使用dynamic_cast转化为所需接口的指针类型


void *cc = new CC();

IB *ibb = dynamic_cast<IB*>((IA*)cc);

尝试下面的都是错误的


void *cc = new CC();

IB *ibb = dynamic_cast<IB*>((IB*)cc);//依旧调用IA的虚函数

IB *ibb = dynamic_cast<IB*>(cc);//编译错误,dynamic_cast不能使用void*类型参数

ibb->funB();

修改后的start函数代码


void start(void *arg){



    IA *ia = (IA*)arg;

    ia->funA();

    IB *ib = dynamic_cast<IB*>((IA*)arg);

    ib->funB();

}

main函数


int main(){

    CC *cc = new CC();

    start(cc);

    return 0;

}

正确的输出


g++ a.cpp

a

IA func

IB func

除此之外


CC *cc = new CC();

IA *ia = (IA *)cc;//正确的指向IA虚函数表

IB *ib = (IB *)cc;//正确的指向IB虚函数表

IB *ibb = (IB *)ia;//错误的指向IA虚函数表

IB *ibbb = dynamic_cast<IB*>(ia);//正确的指向IB虚函数表