多重继承下父子类之间的类型转换

一、前言

在日常工作中踩了一个坑,现象是:从一个dll里获取导出类A,然后调用类A的FunA,结果FunA没有被调用,相关代码简化如下:

class AImpl : public OtherClass, public A
{
	// OhterClass 不包含类A的FunA
    ...
    // 用户通过该函数获取A的指针,结果用A的指针调用FunA失败
    void Query(void ** ptr)
    {
        *ptr = this;
    }
}

最后定位到的原因是:多重继承下,直接赋值指针是没有进行类型转换的,其仅仅只是单纯赋值,这导致this指针没有类型转换为A类,所以A类的FunA不会被调用
踩到这个坑说明我对C++的多重继承下的类型转换完全不了解,所以就搞个文章梳理下~

二、情景模拟复现

原工程项目比较庞大,不利于学习研究,所以把它抽成下面这个demo:

class ChildA
{
public:
	ChildA(){}
	virtual ~ChildA(){}

	virtual void FunA() = 0;
	virtual void Fun() {printf("childa fun\n");}

};

class ChildB
{
public:
	ChildB(){}
	virtual~ChildB() {}

	virtual void FunB(){};
	virtual void Fun() {printf("childb fun\n");}
};

class Child : public ChildA, public ChildB
{
public:
	Child(){}
	virtual ~Child(){}

	virtual void FunA(){printf("funa\n");}
	virtual void FunB(){printf("funb\n");}
	virtual void Fun() {printf("child fun\n");}

	void Query(void ** ptr)
	{
		*ptr = this;
	}

	void QueryB(void ** ptr)
	{
		*ptr = dynamic_cast<ChildB *>(this);
	}

};

class ChildBase : public ChildB
{
public:
	virtual void FunB(){printf("ChildBase funb\n");}
	virtual void Fun() {printf("ChildBase fun\n");}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Child A;
	// 情况1:直接将指针赋值
	printf("情况1:\n");
	ChildB * ptr = NULL;
	A.Query((void**)&ptr);
	ptr->FunB();

	// 情况2:获取直接赋值的指针后,再调用dynamic_cast进行类型转换
	printf("\n情况2:\n");
	ChildB * ptra = dynamic_cast<ChildB *>(ptr);
	ptra->FunB();

	// 情况3:获取类型转换后的指针
	printf("\n情况3:\n");
	ChildB * ptrb = NULL;
	A.QueryB((void**)&ptrb);
	ptrb->FunB();

	// 情况4:多态
	printf("\n情况4:\n");
	ChildB * ptrDrive = new ChildBase();
	ptrDrive->Fun();
	ptr->Fun();
	ptrb->Fun();

	printf("\nA ptr is 08x%08x, \nptr is 08x%08x, \nptrb is 08x%08x, \nptrDrive is 08x%08x\n", &A, ptr, ptrb, ptrDrive);
	
	return 0;
}

看到这里,不妨可以先思考下这几种情况下输出结果是啥~

三、开始分析

关于多重继承下的类型转换,先瞅瞅这个相关知识:

因为C++编译器没有足够的知识来把Child*类型转换为ChildB*类型,只能按照传统的C指针强制转换处理,也就是指针位置不变。(此处可通过打印指针的值验证)
详情可见:https://blog.csdn.net/smstong/article/details/24455371

接下来就对每个情况进行分析:

1. 情况1

该情况是将子类指针直接赋值给父类指针,即Child*类型赋值给ChildB*类型。该情况下就是强制类型转换,其结果为ptr指向的内存表位置和A的位置一样,类似下图情况:
在这里插入图片描述

在编译器看来,ptr->FunB();就是ptr指针+4的结果,所以结合上图,最后展示的结果就是FunA被调用了。
ps:这时可能有小伙伴会提问,如果ptr指针+4地方没有函数咋整?我理解这就是不可知行为了,相当于访问一个不是你程序分配的内存。所以我们编码时要避免出现情况1。

2. 情况2

该情况和情况1一样,但是这里多了一个步骤:在获取指针后再调用dynamic_cast进行类型转换。但实际上这种情况的指针ptra指向内存位置依然和情况1一致,所以最后也是FunA被调用。没找到相应解释,欢迎大家在评论区提出你的见解,帮忙解惑~

3. 情况3

该情况是在Child类里面进行了动态转换(dynamic_cast),所以通过QueryB方法获取的指针类型就是ChildB类型,所以该指针指向的内存表位置是ChildB类的位置,所以FunB被调用。
以上三种情况是多重继承下类型转换的情况,情况4是探讨多态是否受多重继承影响的问题

4. 情况4

该情况是比较以上3个情况的多态结果。首先要搞明白啥是多态:

多态是一种晚绑定,当父子类有相同的函数名时(该函数被virtual修饰&&父子类之间是通过指针或引用赋值),运行时期才知道调用的父类还是子类的函数。一般都是调用对象类型的函数,即
父类* Father = new 子类; --》调用的是子类方法
父类 & Father = 子类; --》调用的是子类的方法
注:不能子类指针指向父类,例如这样:子类* son = new 父类;编译会出错,除非用类型转换,但也不推荐,认真思考下就晓得啦~

ptrDrive是没有多重继承情况的多态,它明显的就是调用了ChildBase方法而不是ChildB方法。
ptr其实指向的对象是Child类型,所以调用的是Child方法。
ptrb和ptr一样,指向的对象也是Child类型,所以调用的是Child方法。
PS:个人觉得一篇博客讲的关于多态的某个总结挺不错的,摘抄于此:

程序一般运行时,找到类,如果它有基类,再找到它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数,派生类也叫子类,基类也叫父类,这就是虚函数的产生,和类的多态性的体现。
详情可见:https://www.cnblogs.com/alinh/p/9636352.html

四、运行结果

经过上一部分的分析,再看这个结果,应该很好理解了。
在这里插入图片描述

五、总结

多重继承情况下不要强制类型转换,否则结果奇奇怪怪。
为了能得到预期结果,记得用动态转换dynamic_cast

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值