本文详细介绍QT核心机制之一 信号和槽。
我们在此根据Qt源代码一步一步探究其信号槽的实现过程。
核心知识点:
模板元编程技术、 Qt moc预编译机制、 QObject类。
目录
4. 信号槽绑定入口: QObject类的静态模板方法(connect方法)
4.1 从入口开始谈起,先看看QObject类的connect 静态模板方法对于信号和槽绑定的声明及其实现。
1. QObject类介绍:
QObject类是整个Qt框架对象的核心模型,此类提供了大量Qt特性。包括对象的无缝通信机制信号槽、反射、动态属性、定时、对象树生命周期管理等。
2: 相关助手类介绍:
信号槽的本质是两个对象的函数地址映射,下面对信号槽实现过程中的一些助手类进行介绍。
2.1 类型(函数指针)萃取模板元函数。
注:下文中会把类型萃取模板元函数称之为类型萃取器。
FunctionPointer萃取器声明部分(Qt 信号槽绑定过程中会大量使用此萃取器来提取信号槽中的相关类型)
FunctionPointer偏特化版本之一:非CV的成员函数指针类型偏特化版本。
文中仅贴出此偏特化版本。为保证篇幅不至于太长,其他偏特化版本不做介绍基本大同小异。
从图中可以看出,FunctionPointer非CV类的成员函数指针类型偏特化版本主要做以下事情。
a. 提取类成员函数指针类型所属的类类型。b. 提取类成员函数指针类型函数形参参数包类型(形参列表打包到List模板元函数中)。
c. 提取类成员函数指针返回值类型。d. 重定义类成员函数指针类型。
e. 提取类成员函数指针类型的形参数量和是否为类的成员函数枚举值。
f. 提供一个此函数指针类型的分发调用静态方法。
2.2 函数调用器FunctorCall元函数
看看2.1 节萃取器会发现类型萃取包装的call分发函数会转而调用此元函数。(包装主要原因是解信号形参数量参数包)
此元函数的作用是调用指定对象o的成员函数f, 并解包信号函数形参数量参数包。
先记得这两个助手类,信号槽绑定与信号发射过程会大量使用到。
3. 信号槽需要Qt moc预编译器的支持
此处不对Qt moc预编译机制做详细介绍,只是谈谈它做了哪些事。 Qt moc详细实现原理单独博文在介绍。
首先我们看看moc预编译器对包含Q_OBJECT宏的类做了哪些事件。
举个栗子 定义一个TestObject类:
图中源码代码中Q_OBJECT宏必须添加,Qt moc预编译器根据此标识在编译前来生成moc_xx.cpp 实现文件。
编译后产生如下文件:
moc_testobject.cpp
void TestObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<TestObject *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->sig_void(); break;
case 1: _t->sig_int((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: _t->sig_char((*reinterpret_cast< char(*)>(_a[1]))); break;
case 3: _t->sig_qstring((*reinterpret_cast< QString(*)>(_a[1]))); break;
case 4: _t->sig_int_char((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< char(*)>(_a[2]))); break;
case 5: _t->slot1(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (TestObject::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_void)) {
*result = 0;
return;
}
}
{
using _t = void (TestObject::*)(int );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_int)) {
*result = 1;
return;
}
}
{
using _t = void (TestObject::*)(char );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_char)) {
*result = 2;
return;
}
}
{
using _t = void (TestObject::*)(QString );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_qstring)) {
*result = 3;
return;
}
}
{
using _t = void (TestObject::*)(int , char );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_int_char)) {
*result = 4;
return;
}
}
}
}
QT_INIT_METAOBJECT const QMetaObject TestObject::staticMetaObject = { {
QMetaObject::SuperData::link<QObject::staticMetaObject>(),
qt_meta_stringdata_TestObject.data,
qt_meta_data_TestObject,
qt_static_metacall,
nullptr,
nullptr
} };
const QMetaObject *TestObject::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *TestObject::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_TestObject.stringdata0))
return static_cast<void*>(this);
return QObject::qt_metacast(_clname);
}
int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 6)
qt_static_metacall(this, _c, _id, _a);
_id -= 6;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 6)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 6;
}
return _id;
}
// SIGNAL 0
void TestObject::sig_void()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
// SIGNAL 1
void TestObject::sig_int(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
// SIGNAL 2
void TestObject::sig_char(char _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
QMetaObject::activate(this, &staticMetaObject, 2, _a);
}
// SIGNAL 3
void TestObject::sig_qstring(QString _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
QMetaObject::activate(this, &staticMetaObject, 3, _a);
}
// SIGNAL 4
void TestObject::sig_int_char(int _t1, char _t2)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
QMetaObject::activate(this, &staticMetaObject, 4, _a);
}
看到这个文件我想会打消这样一个疑问:为什么声明了信号函数自己没有实现编译器确没有报错?
这是因为Qt moc预编译器在编译器介入之前已经帮你做好了这件事。
moc_testobject.cpp 还定义了一些函数如qt_static_metacall、metaobject、qt_metacast、qt_metacall 为什么定义这些函数?
我们接着来看TestObject类声明中的Q_OBJECT宏内容:
: O_OBJECT 宏声明了与moc_testobject.cpp