C++/Qt运行时多态实现原理及信号槽相关技术疑问
示例代码
class IBase :public QObject { Q_OBJECT }; class Derived :public IBase { Q_OBJECT public slots: void Init(int count) { //Do something } }; class Emitter { signals: void NotifyCountChanged(int); public: void PerformOperation(QObject* item) { connect(this, SIGNAL(NotifyCountChanged(int)), item, SLOT(Init(int))); // 旧风格语法 emit NotifyCountChanged(5); // 发射信号 } }; int main() { IBase *pBase = new Derived(); Emitter emitterObj; emitterObj.PerformOperation(pBase); }
以上代码可正常运行:触发信号后,Derived类中的Init槽函数会被执行。以下是针对疑问的解答:
1. 为何无需在IBase中声明纯虚Init()?QObject*传入后为何能编译通过?
你用的是Qt旧版SIGNAL/SLOT宏语法,这套机制不依赖C++编译期类型检查,核心靠Qt的moc工具在编译前生成元对象信息:
- 宏会把信号和槽的签名转换成字符串,编译时编译器仅检查
connect函数的参数格式合法,不会验证item指向的对象是否真的存在Init(int)槽。 - 运行时Qt会通过
QObject的元对象系统,在item对应的实例中查找匹配签名的槽函数,找到后再执行调用。这和标准C++必须通过虚函数表实现多态的逻辑完全不同,因此基类IBase不需要声明任何虚函数。
2. 新增Derived2类后信号触发的表现?如何实现类似C++的运行时多态?
- 若将
Derived和Derived2的实例都传入PerformOperation,相当于给NotifyCountChanged信号连接了两个槽,触发信号时两个槽都会被执行——因为Qt的信号槽是一对多的关联关系。 - 要实现类似C++“仅执行对应派生类槽函数”的效果,有两种常用方式:
- 改用Qt新信号槽语法(基于函数指针):让基类声明纯虚函数,槽函数调用该虚函数,派生类重写虚函数。示例:
连接时用class IBase : public QObject { Q_OBJECT public: virtual void Init(int count) = 0; public slots: void onInit(int count) { Init(count); } }; class Derived : public IBase { Q_OBJECT public: void Init(int count) override { /* 派生类实现逻辑 */ } };connect(this, &Emitter::NotifyCountChanged, item, &IBase::onInit),运行时会通过虚函数表调用对应派生类的Init实现。 - 确保每个信号仅与一个实例的槽连接,每次连接前断开之前的关联(调用
disconnect)。
- 改用Qt新信号槽语法(基于函数指针):让基类声明纯虚函数,槽函数调用该虚函数,派生类重写虚函数。示例:
3. QMetaObject的实例化逻辑,和C++虚函数多态的关联?
QMetaObject是类级别的单例,并非每个实例创建一个:
- 每个继承
QObject且添加Q_OBJECT宏的类,moc会为它生成一个静态的QMetaObject实例,该类的所有对象共享这个实例。比如Derived和Derived2会各有一个属于自己的静态QMetaObject。 - 它和C虚函数多态的关联在于:
QObject类的metaObject()是虚函数,每个子类的metaObject()会返回自身类对应的静态QMetaObject指针。运行时通过QObject*调用metaObject()时,会根据实际对象的类型(比如Derived)返回对应元对象,这和C通过虚表调用虚函数的逻辑一致——都是靠虚函数表实现运行时类型识别,进而找到对应类的元信息,完成槽函数的查找与调用。
内容的提问来源于stack exchange,提问作者Avi
相关产品推荐
相关产品推荐

