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

C++进阶专栏:http://t.csdnimg.cn/wt01A

目录

1.现象

2.分析流程

3.解决方案

4.总结

5.源码下载地址


1.现象

首先定义了一个消息通知接口:

LinkMessageNotify.h

#pragma once
class ILinkMessageNotify
{
public:
	virtual void notify(int value) = 0;
};

接着定了设备接口和设备类:

IDevice.h

#ifndef  _I_DEVICE_H_
#define  _I_DEVICE_H_
#include <assert.h>

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

public:
	virtual int deviceID() const = 0;
	virtual int type()  const = 0;
	virtual int open() = 0;
	virtual int close() = 0;
	virtual int reset() = 0;
	virtual int state() const = 0;
	virtual int control(int type, const void* pParam, int paramLen) = 0;
	virtual int write(const char*, int) = 0;
	virtual int write(const char*, int, void*) = 0;
	virtual int read(char*, int) = 0;
	virtual int read(char*, int, void*) = 0;
	virtual int setProperty(const char* name, const void* vlaue) = 0;
};

class CDeviceAdapter : public IDevice
{
public:
	virtual ~CDeviceAdapter() {}

public:
	int deviceID() const override { assert(false); return -1; }
	int type() const override { assert(false); return -1; }
	int open() override { assert(false); return -1; }
	int close() override { assert(false); return -1; }
	int reset() override { assert(false); return -1; }
	int state() const override { assert(false); return -1; }
	int control(int type, const void* pParam, int paramLen) override { assert(false); return -1; }
	int write(const char*, int) override { assert(false); return -1; }
	int read(char*, int) override { assert(false); return -1; }
	int write(const char*, int, void*) override { assert(false); return -1; }
	int read(char*, int, void*) override { assert(false); return -1; }
	int setProperty(const char* name, const void* vlaue) { assert(false); return -1; }
};

#endif

这个是我们在项目上用的一个通用设备接口类,涵盖了对设备的控制和数据交互。

LinkDevice.h

#pragma once
#include "IDevice.h"
#include "LinkMessageNotify.h"

class CLinkDevice : public CDeviceAdapter
{
public:
	explicit CLinkDevice();

public:
	int write(const char* data, int len) override;
	int setProperty(const char* name, const void* value) override;

private:
	ILinkMessageNotify* m_pMessageNotify;
};

LinkDevice.cpp

#include "LinkDevice.h"

CLinkDevice::CLinkDevice()
	: m_pMessageNotify(nullptr)
{

}

int CLinkDevice::write(const char* data, int len)
{
	if (m_pMessageNotify) {
		m_pMessageNotify->notify(2142);
	}
	return 1;
}
int CLinkDevice::setProperty(const char* name, const void* value)
{
	m_pMessageNotify = (ILinkMessageNotify*)static_cast<const ILinkMessageNotify*>(value);
	return 1;
}

CLinkDevice是具体的设备对象,目前只实现了write和setProperty接口,因为在这里只是测试就没有实现更多的接口。

LinkMainWindow.h

#pragma once
#include <QWidget>
#include "LinkMessageNotify.h"
#include "IDevice.h"

class CLinkMainWindow : public QWidget
	                  , public ILinkMessageNotify
{
	Q_OBJECT

public:
	explicit CLinkMainWindow(IDevice* pDevice, QWidget* parent = nullptr);

public:
	void notify(int value) override;

private:
	int m_value;
};

LinkMainWindow.cpp

#include "LinkMainWindow.h"

CLinkMainWindow::CLinkMainWindow(IDevice* pDevice, QWidget* parent)
	: QWidget(parent)
{
	pDevice->setProperty("NotifyInterface", this);
}

void CLinkMainWindow::notify(int value)
{
	m_value = value;

    //界面显示等等,此处省略。。。
    //...
}

CLinkMainWindow是设备关联的窗口类,从上述代码可以看到在CLinkMainWindow的构造函数中把自己注入到CLinkDevice对象中,CLinkDevice一旦有新的消息就会立即通知窗口显示。

测试用例

#include "MyTest.h"
#include "LinkMainWindow.h"
#include "LinkDevice.h"

MyTest::MyTest(QWidget* parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

	init();
}

MyTest::~MyTest()
{}

void MyTest::init()
{
	QObject::connect(ui.pushButton, &QPushButton::clicked, this, [&]() {
            //[1]
		    CLinkDevice device;
            //[2]
		    CLinkMainWindow mainWindow(&device);
		    //[3]
            device.write(nullptr, 0);
		});
}

上述代码模拟了整个流程;正常情况下,程序最后会走到CLinkMainWindow::notify函数中,但是事实真的会如此吗?

我们断点调试,结构走到了如下图的代码位置:

看到这个现象刚开始一脸懵逼,怎么会走到这里呢?本来该走到CLinkMainWindow::notify的,但是却跑到了QMetaObject *CLinkMainWindow::metaObject()。

2.分析流程

1)  仔细思考一下,虚函数调错了,首先怀疑notify函数是不是跟系统函数冲突了,但是我查询了QWidget、QObject都没有notify函数,所以肯定不存在冲突。

2)接着怀疑对象CLinkDevice中的m_pMessageNotify指针指向错地方了,于是断点调试查看指针的指向:

发现m_pMessageNotify的虚表_vfptr执行的虚函数地址不对,通过比对,_vfptr是QObject的虚表指针,意思是m_pMessageNotify的_vfptr指向的是QObject的虚函数表地址,QObject的虚表为:

由于ILinkMessageNotify只有一个虚接口,因此调用notify就调到了QMetaObject *CLinkMainWindow::metaObject() 处。

3)通过上面的分析,那么是哪一个步骤错了呢?层层分析,锁定在了CLinkDevice::setProperty的位置,语句m_pMessageNotify = (ILinkMessageNotify*)static_cast<const ILinkMessageNotify*>(value); 强制转换指针出错了。CLinkMainWindow内存对象模型如下:

通过static_cast强制转换就把QWidge*转换成了ILinkMessageNotify*,QWidge的父类是QObject,调用notify函数就相当于调用了QObject的虚函数metaObject()。

3.解决方案

通过前面的分析,找到了问题的根本原因是用static_cast强制转换指针导致的。

C++之数据转换(全)_c++数据类型转换-CSDN博客

所以在CLinkMainWindow的构造函数调用setProperty之前就要把this指针转换成ILinkMessageNotify*,于是修改为:

ILinkMessageNotify* pNotify = this;
pDevice->setProperty("NotifyInterface", pNotify);

再次调试,结果对了。

4.总结

本文涉及到的知识点有:

1) C++对象的内存模型(内存布局)

2)数据转换static_cast的用法,延伸的还有const_cast、dynamic_cast、reinterpret_cast

5.源码下载地址

运行环境:VS2019、Qt5.12.12

https://download.csdn.net/download/haokan123456789/89120988?spm=1001.2014.3001.5501

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值