一、Qt中.ui文件的作用原理 在qtcreator中新建项目时,会自动给我们生成widget.h和widget.cpp文件。其中widget.h会自动给我们一下内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <QWidget> namespace Ui {class Widget ;} class Widget : public QWidget{ Q_OBJECT public : explicit Widget (QWidget *parent = 0 ) ; ~Widget (); private : Ui::Widget *ui; };
这里面有两个Widget类,Ui::Widget与::Widget(我已这样的方式区分两个类)。
直入正题的说,界面文件夹下的widget.ui文件就是在定义Ui::Widget类。
widget.ui文件以xml语言的方式在描述Ui::Widget的定义,然后Qt框架解析这个ui文件,生成对应cpp代码,由此来实现Ui::Widget的定义。
通过一个简单的例子来了解如何从widget.ui变成cpp代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="UTF-8" ?> <ui version ="4.0" > <class > Widget</class > // 表明当前文件是在定义Ui::Widget类 <widget class ="QWidget" name ="Widget" > // 表明setupUi传入的参数是QWidget* widget <property name ="geometry" > <rect > <x > 0</x > <y > 0</y > <width > 300</width > <height > 150</height > </rect > </property > <property name ="windowTitle" > <string > 窗口标题</string > </property > <widget class ="QPushButton" name ="pushButton" > // Ui::Widget下的一个控件 <property name ="text" > <string > Click Me</string > </property > </widget > <widget class ="QLineEdit" name ="lineEdit" > // Ui::Widget下的另一个控件 </widget > </ui >
对应生成的cpp代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 namespace Ui { class Widget ; } class Ui_Widget { public : QPushButton *pushButton; QLineEdit *lineEdit; void setupUi (QWidget *Widget) { Widget->setObjectName ("Widget" ); Widget->resize (300 , 150 ); Widget->setWindowTitle ("窗口标题" ); pushButton = new QPushButton (Widget); pushButton->setObjectName ("pushButton" ); pushButton->setText ("Click Me" ); lineEdit = new QLineEdit (Widget); lineEdit->setObjectName ("lineEdit" ); } }; namespace Ui { class Widget : public Ui_Widget {}; }
或许会疑惑,为什么widget.ui的顶层控件<widget>并不在Ui_Widget中作为属性存在,只有其下的子控件作为属性存在。而且setupUi()中也没有去new它。
其实,顶层控件<widget>已经由用户去创建了,::Widget这个由用户定义的类就是这个顶层控件,它作为setupUi()的参数传进去。
setupUi()函数的本质就是在创建控件(设置它们的父类为我们在main.cpp中创建的Widget),然后给各个子控件赋予属性。
setupUi()的作用就是1、new出各个子控件,2、把各个子控件挂到顶层控件上
至于为什么Ui::Widget下定义的属性都是指针,可以看看下面三条原因(我认为第三条比较靠谱:
我自己的推测是,用户可以自定义控件,但是Qt框架在定义时并不知道用户自定义的控件会有多大,所以干脆用指针来替代
核心原因有三点:
1. 生命周期由 Qt 父子树管理 控件在 setupUi() 里通过 new 创建在堆上,并被设为父控件的子对象:
1 2 3 4 void setupUi (QWidget *Widget) { pushButton = new QPushButton (Widget); }
Qt 的父子机制保证:父对象析构时,会自动 delete 所有子对象 。所以这些控件存活在堆上直到窗口关闭,而不是随 Ui_Widget 这个壳对象的生命周期。
2. QObject 不可拷贝 所有 Qt 控件继承自 QObject,而 QObject 禁用了拷贝构造和赋值:
1 2 3 class QObject { Q_DISABLE_COPY (QObject) };
如果写成值成员:
用指针就没有这个问题,Ui_Widget 对象本身只是持有指针,拷贝/移动不受影响(虽然一般也不会去拷贝它)。
3. 头文件只需前向声明 用指针的话,头文件里只写 class QPushButton; 就够了,不需要 #include <QPushButton>,编译依赖更少、速度更快。值成员则必须有完整的类定义。
1 2 3 4 5 class QPushButton ; class Ui_Widget { QPushButton *pushButton; };
简而言之:堆分配 + Qt 父子树自动管理了生命周期,指针是这套机制的必然选择。
二、Qt中connect连接信号和槽机制的原理 为什么可以在方法体内直接使用 connect connect 是 QObject 的 public static 成员函数。所有继承自 QObject 的类,在成员函数内部调用 connect 时,编译器通过名字查找找到这个静态函数,等价于 QObject::connect(...)。
connect 的三个重载 重载都用函数重名,不涉及虚函数机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static QMetaObject::Connection connect ( const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType = Qt::AutoConnection) ;template <typename Func1, typename Func2>static QMetaObject::Connection connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType = Qt::AutoConnection) ;template <typename Func1, typename Func2>static QMetaObject::Connection connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot) ;
所有重载最终汇聚到 QObjectPrivate::connect()。
重载1:SIGNAL/SLOT 宏(Qt4 遗留方式) 宏的定义 1 2 3 4 #define SIGNAL(a) "1" a #define SLOT(a) "2" a #define METHOD(a) "0" a
所以:
1 2 3 connect (btn, SIGNAL (clicked ()), this , SLOT (onClicked ()));connect (btn, "1clicked()" , this , "2onClicked()" );
connect 入口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 QMetaObject::Connection QObject::connect (const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type) { if (sender == nullptr || receiver == nullptr || signal == nullptr || method == nullptr ) { qWarning ("QObject::connect: invalid null parameter" ); return QMetaObject::Connection (); } QByteArray tmp_signal_name; const QMetaObject *smeta = sender->metaObject (); ++signal; QByteArray signalName = QMetaObject::normalizedSignature (signal); int signal_index = smeta->indexOfSignal (signalName.constData ()); if (signal_index < 0 ) { signal_index = smeta->indexOfMethod (signalName.constData ()); } ++method; const QMetaObject *rmeta = receiver->metaObject (); QByteArray methodName = QMetaObject::normalizedSignature (method); int method_index = rmeta->indexOfSlot (methodName.constData ()); if (method_index < 0 ) method_index = rmeta->indexOfMethod (methodName.constData ()); if (signal_index < 0 || method_index < 0 ) { qWarning ("QObject::connect: No such signal/slot ..." ); return QMetaObject::Connection (); } if (!QMetaObjectPrivate::check_signal_macro (sender, signal_index, receiver, method_index)) { qWarning ("QObject::connect: signal/slot argument mismatch ..." ); return QMetaObject::Connection (); } QMetaObject::Connection handle = QMetaObject::Connection (); QMetaObjectPrivate::connectImpl (sender, signal_index, receiver, method_index, 0 , type, 0 , smeta); handle.d_ptr->sender = const_cast <QObject*>(sender); return handle; }
缺点 :签名检查发生在运行时,拼写错误只有在运行时才能被发现。
重载2:成员函数指针(类型安全) 1 connect (sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged);
FunctionPointer 萃取模板1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 namespace QtPrivate {template <typename T> struct FunctionPointer { };template <class Obj , typename Ret, typename ... Args>struct FunctionPointer <Ret (Obj::*)(Args...)>{ typedef Obj Object; typedef List<Args...> Arguments; enum { IsPointerToMemberFunction = true }; static const int *argumentTypes () { static const int types[] = { (QtPrivate::QMetaTypeIdHelper<Args>::qt_metatype_id ()...) }; return types; } }; }
connect 模板入口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 template <typename Func1, typename Func2>inline QMetaObject::Connection QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type) { typedef QtPrivate::FunctionPointer<Func1> SignalType; typedef QtPrivate::FunctionPointer<Func2> SlotType; Q_STATIC_ASSERT_X ( (QtPrivate::CheckCompatibleArguments<SignalType, SlotType>::value), "Signal and slot arguments are not compatible." ); const int *signalTypes = SignalType::argumentTypes (); const int *slotTypes = SlotType::argumentTypes (); return QObject::connectImpl ( sender, reinterpret_cast <void **>(&signal), receiver, reinterpret_cast <void **>(&slot), new QtPrivate::QSlotObject <Func2, typename SignalType::Arguments>(slot), type, signalTypes, &SignalType::Object::staticMetaObject ); }
QSlotObject — 用函数指针把槽包装起来1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 template <typename Func, typename Args>struct QSlotObject : public QSlotObjectBase{ Func function; QSlotObject (Func f) : function (f) {} static void impl (int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret) { QSlotObject *t = static_cast <QSlotObject *>(this_); switch (which) { case 0 : t->function (r, (*reinterpret_cast <typename RemoveRef<Args>::Type *>(a[1 ])), ...); break ; case 1 : break ; } } };
connectImpl 的调用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 QObject::connect(sender, &S::sig, receiver, &R::slot, type) │ ├─ FunctionPointer 萃取 sender/receiver 类型、参数类型 ├─ 编译期类型检查 (COMPILE TIME ← 关键优势) ├─ 创建 QSlotObject 包装槽函数指针 │ └─ QObject::connectImpl(sender, &signal_ptr, receiver, &slot_ptr, slotObj, type, types, &metaObject) │ ├─ 拿到 signal_index(通过 sender→metaObject()→indexOfSignal) ├─ 拿到 method_index (通过 receiver→metaObject()→indexOfSlot) │ └─ QObjectPrivate::connectImpl(sender, signal_index, receiver, method_index, slotObj, type, types) │ └─ QObjectPrivate::connect(同下)
重载3:Lambda / Functor 1 2 connect (sender, &Sender::valueChanged, [](int v) { qDebug () << v; });
模板版本(无 receiver 参数的那个):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 template <typename Func1, typename Func2>inline QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot) { return connect (sender, signal, sender, slot, Qt::DirectConnection); } template <typename Func1, typename Func2>QMetaObject::Connection QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot, Qt::ConnectionType type) { auto slotObj = new QtPrivate::QFunctorSlotObject<Func2, typename QtPrivate::FunctionPointer<Func1>::Arguments>(slot); return QObject::connectImpl (sender, reinterpret_cast <void **>(&signal), context, nullptr , slotObj, type, signalTypes, &metaObject); }
QFunctorSlotObject 如何调用 lambda 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 template <typename Func, typename Args>struct QFunctorSlotObject : public QSlotObjectBase{ Func function; QFunctorSlotObject (Func f) : function (std::move (f)) {} static void impl (int which, QSlotObjectBase *this_, QObject *, void **a, bool *ret) { QFunctorSlotObject *t = static_cast <QFunctorSlotObject *>(this_); switch (which) { case 0 : t->function (reinterpret_cast <Args *>(a[0 ])->data ()...); break ; } } };
汇聚点:QObjectPrivate::connect() 底层数据结构 三种重载最终都走到这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class QObjectPrivate : public QObjectData{ public : struct Connection { QObject *sender; QObject *receiver; QAtomicInt ref_; uint id; union { StaticMetaCallFunction callFunction; QSlotObjectBase *slotObj; }; int signal_index; int method; ushort method_relative; Qt::ConnectionType connectionType; uint isSlotObject : 1 ; uint slotThread : 1 ; Connection **prev; Connection *nextConnectionList; QAtomicPointer<const int > argumentTypes; }; QVector<ConnectionList> connectionLists; typedef Connection *ConnectionList; };
connect 插入链表的过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 QMetaObject::Connection QObjectPrivate::connect ( const QObject *sender, int signal_index, const QObject *receiver, int method_index, QSlotObjectBase *slotObj, int type, int *types) { QObject *s = const_cast <QObject *>(sender); QObject *r = const_cast <QObject *>(receiver); Connection *c = new Connection; c->sender = s; c->receiver = r; c->signal_index = signal_index; c->method = method_index; c->connectionType = type; c->isSlotObject = (slotObj != 0 ); if (slotObj) { c->slotObj = slotObj; slotObj->ref (); } else { c->callFunction = ...; } QMutexLocker locker (&s->d_func()->mutex_lock) ; if (s->d_func ()->connectionLists.size () <= signal_index) s->d_func ()->connectionLists.resize (signal_index + 1 ); ConnectionList &list = s->d_func ()->connectionLists[signal_index]; c->prev = reinterpret_cast <Connection **>(&list); c->nextConnectionList = list; if (list) list->prev = &c->nextConnectionList; list = c; QObjectPrivate::get (r)->addConnection (c); return c->toConnection (s); }
最终形成的数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sender (QObject) d_ptr → QObjectPrivate connectionLists[]: QVector<Connection*> [0] → nullptr ← 信号 destroyed() 无连接 [1] → Connection⁰ ← 信号 valueChanged(int) ├─ sender: btn ├─ receiver: this ├─ signal_index: 1 ├─ method: 3 (onValueChanged 在 receiver metaObject 中的索引) ├─ slotObj / callFunction ├─ nextConnectionList → Connection¹ ← 另一个槽连到同一信号 │ └─ ... └─ prev: ← &connectionLists[1] [2] → nullptr ...
receiver 端同样维护了反向链表,使得 receiver 析构时可以遍历所有指向它的 Connection 并断开。
信号的发射 信号的真面目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 signals: void valueChanged (int newValue) ; void MyWidget::valueChanged (int _t1) { void *_a[] = { Q_NULLPTR, const_cast <void *>(reinterpret_cast <const void *>(&_t1)) }; QMetaObject::activate (this , &staticMetaObject, 1 , _a); }
emit 宏的定义:
activate — 遍历链表逐个调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 void QMetaObject::activate (QObject *sender, const QMetaObject *m, int local_signal_index, void **argv) { int signal_index = local_signal_index + m->methodOffset (); QObjectPrivate *d = sender->d_func (); if (!d->connectionLists.isEmpty ()) { QMutexLocker locker (&d->mutex_lock) ; ConnectionList &list = d->connectionLists[signal_index]; Connection *c = list; while (c != 0 ) { QObject *receiver = c->receiver; if (!receiver) { c = c->nextConnectionList; continue ; } if (c->connectionType == Qt::DirectConnection || (c->connectionType == Qt::AutoConnection && sender->thread () == receiver->thread ())) { if (c->isSlotObject) { c->slotObj->call (receiver, argv); } else { receiver->metaObject ()->static_metacall ( receiver, QMetaObject::InvokeMetaMethod, c->method, argv ); } } else { QMetaCallEvent *ev = c->isSlotObject ? new QMetaCallEvent (c->slotObj, 0 , signal_index, argv) : new QMetaCallEvent (c->method, 0 , ..., argv); QCoreApplication::postEvent (receiver, ev); } c = c->nextConnectionList; } } }
总结:一次 connect + emit 的全貌 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ┌─ 编译期 ─────────────────────────────────────────┐ │ connect(sender, &S::sig, receiver, &R::slot) │ │ │ │ │ ├─ FunctionPointer 萃取类型 │ │ ├─ 编译期参数兼容性检查 │ │ ├─ QSlotObject 包装槽函数指针 │ │ └─ → QObjectPrivate::connect() │ │ └─ Connection 节点插入 │ │ sender.connectionLists[signal_index] │ │ (链表头插法) │ └───────────────────────────────────────────────────┘ ┌─ 运行时 ─────────────────────────────────────────┐ │ emit sig(args) │ │ │ │ │ └─ MOC生成的 sig() 函数体 │ │ └─ QMetaObject::activate(sender, metaObject,│ │ signal_index, argv)│ │ │ │ │ └─ 遍历 connectionLists[signal_index] │ │ │ │ │ ├─ 同线程 → 直接调用 slot / lambda│ │ └─ 跨线程 → postEvent 排队 │ └───────────────────────────────────────────────────┘
信号的本质 :就是一个由 MOC 生成的 protected 成员函数,函数体只做一件事——把参数打包成 void*[],然后调用 QMetaObject::activate()。信号的返回值永远是 void(MOC 强制)。emit 是个空宏,它的存在纯属给人类读者一个视觉标记。
Qt5.8 信号槽机制深度解析 下面从 connect 三种重载 、核心数据结构 、信号发射调用链 三个层面展开,结合关键源码文件说明。
一、connect 的三种重载方式 1. SIGNAL/SLOT 宏重载(字符串形式) 1 2 3 4 5 static QMetaObject::Connection connect ( const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) ;
内部流程 (qobject.cpp:3473 附近):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 QMetaObject::Connection QObject::connect (const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type) { const QByteArray signalSignature = QMetaObjectPrivate::signalName (signal); const QByteArray methodSignature = QMetaObjectPrivate::methodName (method); const QMetaObject *smeta = sender->metaObject (); int signal_index = smeta->indexOfSignal (signalSignature.constData ()); if (signal_index < 0 ) { } const QMetaObject *rmeta = receiver->metaObject (); int method_index = rmeta->indexOfSlot (methodSignature.constData ()); if (method_index < 0 ) { } return QMetaObjectPrivate::connect (sender, signal_index, smeta, receiver, method_index, rmeta, type); }
关键点 :
SIGNAL(x) 宏展开为 "2" #x,即 "2valueChanged(int)",数字前缀用于区分信号/槽的规范化签名
每次调用都要做 字符串解析和线性查找 ,没有编译期类型检查
查找到的 signal_index 和 method_index 是 MOC 生成的 meta-object 表中的相对偏移
2. 成员函数指针重载(类型安全) 1 2 3 4 5 6 7 8 template <typename Func1, typename Func2>static inline QMetaObject::Connection connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection) ;
内部流程 (qobject.h 模板,调用到 qobject.cpp:3786 附近的 QObject::connectImpl):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 template <typename Func1, typename Func2>QMetaObject::Connection QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type) { typedef QtPrivate::FunctionPointer<Func1> SignalType; typedef QtPrivate::FunctionPointer<Func2> SlotType; Q_STATIC_ASSERT_X (QtPrivate::HasQ_OBJECT_Macro<...>::Value, ...); const int *types = 0 ; return QObject::connectImpl (sender, reinterpret_cast <void **>(&signal), receiver, reinterpret_cast <void **>(&slot), new QtPrivate::QSlotObject <Func2, ...>(slot), type, types, &SignalType::Object::staticMetaObject); }
关键数据结构 QSlotObjectBase (qobjectdefs_impl.h):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct QSlotObjectBase { QAtomicInt m_ref; typedef void (*Operation) (QSlotObjectBase *self, ...) ; Operation impl; }; template <typename Func, typename Args, typename R>class QFunctorSlotObject : public QSlotObjectBase { Func function; };
关键点 :
QtPrivate::FunctionPointer 模板在编译期萃取返回值类型、参数类型、类类型
不依赖字符串查找,通过 &ClassName::methodName 编译器直接确定函数在虚表中的偏移
最终调用 QObject::connectImpl,将槽包装为 QSlotObjectBase 子类对象
3. Lambda/匿名函数重载 1 2 3 4 5 template <typename Func1, typename Func2>static inline QMetaObject::Connection connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot) ;
内部流程 :
1 2 3 4 5 6 7 8 9 10 11 12 template <typename Func1, typename Func2>QMetaObject::Connection QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot) { return connect (sender, signal, sender, new QtPrivate::QFunctorSlotObject <Func2, ...>(slot), type); }
关键点 :
Lambda 被包装进 QFunctorSlotObject<Func2>,Func2 是编译期确定的闭包类型
当不传 receiver 时,默认以 sender 作为上下文对象 ——sender 析构时连接自动断开
QSlotObjectBase::impl 函数指针在构造时被设为模板特化的 call 静态函数,调用时直接执行 function(args...)
二、核心数据结构 QObjectPrivate::Connection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct QObjectPrivate ::Connection { QObject *sender; QObject *receiver; QSlotObjectBase *slotObj; QObjectPrivate::Connection *nextConnectionList; Connection **prevConnectionList; ushort method_offset; ushort method_relative; uint signal_index : 27 ; ushort connectionType : 3 ; ushort isSlotObject : 1 ; ushort ownSlotObj : 1 ; };
QObjectPrivate::ConnectionList 1 2 3 4 struct QObjectPrivate ::ConnectionList { QObjectPrivate::Connection *first; QObjectPrivate::Connection *last; };
ConnectionLists(信号→连接列表的映射) 1 2 3 4 5 6 7 8 9 QObjectPrivate::ConnectionList *connectionLists; class QObjectConnectionListVector : public QVector<ConnectionList> { };
整体关系图 :
1 2 3 4 5 6 7 8 9 10 11 QObject └── QObjectPrivate └── connectionLists (QObjectConnectionListVector) ├── [0] ConnectionList → Connection → Connection → ... ├── [1] ConnectionList → Connection → ... ├── [2] ConnectionList → (empty) ├── [3] ConnectionList → Connection → ... └── ... 每个 ConnectionList 对应一个信号索引(signal_index) 每个 Connection 是一个链表节点,记录了一对 sender-receiver 的连接关系
三种重载最终都汇入这里(qobject.cpp:3546):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 QMetaObject::Connection QMetaObjectPrivate::connect ( const QObject *sender, int signal_index, const QMetaObject *senderMetaObject, const QObject *receiver, int method_index, const QMetaObject *receiverMetaObject, int type, int *types, QSlotObjectBase *slotObj) { QObject *s = const_cast <QObject *>(sender); int signal_absolute_index = signal_index + senderMetaObject->methodOffset (); QObjectPrivate::ConnectionList *connectionList = QObjectPrivate::get (s)->ensureConnectionData (signal_absolute_index); QObjectPrivate::Connection *c = new QObjectPrivate::Connection; c->sender = s; c->receiver = const_cast <QObject *>(receiver); c->slotObj = slotObj; c->signal_index = signal_absolute_index; c->method_relative = method_index; c->method_offset = receiverMetaObject->methodOffset (); c->connectionType = type; c->isSlotObject = (slotObj != 0 ); c->ownSlotObj = (slotObj != 0 ); c->prevConnectionList = &connectionList->last; if (connectionList->last) { connectionList->last->nextConnectionList = c; c->prevConnectionList = &connectionList->last->nextConnectionList; } else { connectionList->first = c; } connectionList->last = c; QObjectPrivate::get (receiver)->addToSenderList (c); QMetaObject::Connection handle (c, signal_absolute_index) ; return handle; }
四、信号发射的完整调用链 当你写 emit someSignal(arg) 时:
Step 1: MOC 生成的信号函数体 1 2 3 4 5 6 7 8 void MyClass::someSignal (int _t1) { void *_a[] = { Q_NULLPTR, const_cast <void *>(reinterpret_cast <const void *>(&_t1)) }; QMetaObject::activate (this , &staticMetaObject, 1 , _a); }
signals 本质上就是 protected 的普通成员函数 ,MOC 为你生成了函数体。emit 宏是空宏,纯粹用于可读性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void QMetaObject::activate (QObject *sender, const QMetaObject *m, int local_signal_index, void **argv) { int signal_index = local_signal_index + m->methodOffset (); QObjectPrivate *d = QObjectPrivate::get (sender); if (d->connectionLists && signal_index < d->connectionLists->count ()) { const QObjectPrivate::ConnectionList *list = &(*d->connectionLists)[signal_index]; if (list) { QObjectPrivate::Connection *c = list->first; if (c) { if (!d->isSender) { d->isSender = true ; QMetaObjectPrivate::activateHelper (sender, signal_index, c, argv, 0 , 0 ); d->isSender = false ; } } } } activate0 (sender, local_signal_index, argv); }
Step 3: activateHelper 遍历链表并分发(qobject.cpp) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static void activateHelper (QObject *sender, int signal_index, QObjectPrivate::Connection *c, void **argv) { QVarLengthArray<QObjectPrivate::Connection *> connections; while (c) { connections.append (c); c = c->nextConnectionList; } for (int i = 0 ; i < connections.count (); ++i) { QObjectPrivate::Connection *conn = connections[i]; if (!conn->receiver) continue ; Qt::ConnectionType type = conn->connectionType; if (type == Qt::AutoConnection) { type = (conn->receiver->thread () == sender->thread ()) ? Qt::DirectConnection : Qt::QueuedConnection; } switch (type) { case Qt::DirectConnection: callSlot (conn->receiver, conn, argv); break ; case Qt::QueuedConnection: { QMetaCallEvent *ev = new QMetaCallEvent (conn, signal_index, argv); QCoreApplication::postEvent (conn->receiver, ev); break ; } case Qt::BlockingQueuedConnection: QMetaCallEvent *ev = new QMetaCallEvent (conn, signal_index, argv); QCoreApplication::postEvent (conn->receiver, ev); ev->semaphore.acquire (); break ; } } }
Step 4: callSlot → 实际执行(qobject.cpp)==最精华的部分== 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static void callSlot (QObject *receiver, QObjectPrivate::Connection *c, void **argv) { if (c->isSlotObject) { c->slotObj->impl (c->slotObj, receiver, argv, ...); } else { const QMetaObject *meta = receiver->metaObject (); int method = c->method_relative + c->method_offset; meta->static_metacall (receiver, QMetaObject::InvokeMetaMethod, method, argv); } }
qt_static_metacall 是 MOC 生成的巨大 switch 函数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 void MyClass::qt_static_metacall (QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { MyClass *_t = static_cast <MyClass *>(_o); switch (_id) { case 0 : _t ->someSignal ((*reinterpret_cast <int *>(_a[1 ]))); break ; case 1 : _t ->onSomeSlot ((*reinterpret_cast <int *>(_a[1 ]))); break ; } } }
五、完整调用链总结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 emit sender->signal(args) │ ▼ MOC 生成的 signal 函数体 QMetaObject::activate(this, &staticMetaObject, signal_index, args) │ ▼ QMetaObject::activate() │ 计算 signal_absolute_index │ 从 connectionLists[signal_absolute_index] 取得 ConnectionList │ ▼ activateHelper() │ 遍历 Connection 链表 │ 判断连接类型 (Direct / Queued / BlockingQueued) │ ├── DirectConnection ──► callSlot() ──► slotObj->impl() 或 qt_static_metacall() │ │ │ ▼ │ Receiver::slotFunction(args) │ ├── QueuedConnection ──► QCoreApplication::postEvent(receiver, QMetaCallEvent) │ │ │ ▼ (receiver 线程的事件循环) │ QMetaCallEvent 出队 → callSlot() → slotFunction() │ └── BlockingQueuedConnection ──► postEvent + semaphore.acquire() │ ▼ (receiver 线程执行完毕) semaphore.release() → 发送线程继续
六、关键源码文件索引
文件
内容
src/corelib/kernel/qobject.cpp
connect() 实现、activate()、activateHelper()、callSlot()、QMetaObjectPrivate::connect()
src/corelib/kernel/qobject_p.h
QObjectPrivate、Connection 结构体、ConnectionList
src/corelib/kernel/qobject.h
三种 connect() 模板重载声明
src/corelib/kernel/qobjectdefs.h
SIGNAL/SLOT 宏、QMetaObject::Connection
src/corelib/kernel/qobjectdefs_impl.h
QSlotObjectBase、QFunctorSlotObject、FunctionPointer trait
moc_xxx.cpp(MOC 生成)
qt_static_metacall()、信号函数体
核心思想可以归纳为:connect 构建了一个以信号索引为 key、以 Connection 链表为 value 的映射表;信号发射时查表遍历链表,按连接类型分派执行。 新式 connect 用编译期模板萃取类型信息替代运行期字符串查找,是 Qt5 相比 Qt4 最大的改进。
这个问题问到了 Qt 信号槽机制的分派核心。答案是:区分不是通过运行时判断,而是通过编译期模板实例化 。不同的 connect 重载实例化出不同的 QSlotObjectBase 子类,每个子类有自己的 impl 函数指针,调用约定在编译期就已确定。
1 2 3 我的疑问是,connect不管传入的是成员函数指针还是lambda表达式,最终都会传入receiver指针。对于成员函数指针在调用时需要传 入receiver但是lambda表达式调用时却不需要。那么Qt在调用时是如何区分辨别是否需要传入receiver指针作为参数的。结合Qt5.8源 码讲讲
一、QSlotObjectBase 的多态机制(手动的虚表) QSlotObjectBase 不用虚函数,而是手动维护了一个函数指针 impl(qobjectdefs_impl.h):
1 2 3 4 5 6 7 8 9 10 11 12 13 struct QSlotObjectBase { QAtomicInt m_ref; typedef void (*ImplFn) (int which, QSlotObjectBase *self, QObject *receiver, void **args, bool *ret) ; ImplFn impl; enum Operation { Call, Compare, NumOperations }; explicit QSlotObjectBase (ImplFn fn) : m_ref(1 ), impl(fn) { } };
三个操作用 which 参数区分:Call 执行调用、Compare 用于 disconnect 比较。每个子类的 impl 函数看到 which == Call 时执行自己的调用逻辑。
二、三种子类,三种调用方式 子类 1:成员函数指针 → QSlotObject qobjectdefs_impl.h(模板参数 Receiver 是接收对象的类型):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 template <typename Receiver, typename Func, typename Args, typename R>struct QSlotObject : public QSlotObjectBase{ typedef void (Receiver::*FuncType) () ; FuncType function; static void impl (int which, QSlotObjectBase *self_, QObject *r, void **a, bool *ret) { QSlotObject *self = static_cast <QSlotObject *>(self_); switch (which) { case Call: { Receiver *receiver = static_cast <Receiver *>(r); (receiver->*self->function)(UnpackType<Args>::unpack (a)...); break ; } case Compare: { break ; } } } };
这里 r(接收者指针)被强转成 Receiver* 当 this 用了。
子类 2:Lambda / 仿函数 → QFunctorSlotObject 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 template <typename Func, typename Args, typename R>struct QFunctorSlotObject : public QSlotObjectBase{ Func function; static void impl (int which, QSlotObjectBase *self_, QObject *r, void **a, bool *ret) { QFunctorSlotObject *self = static_cast <QFunctorSlotObject *>(self_); switch (which) { case Call: call (self->function, UnpackType<Args>::unpack (a)...); break ; } } };
r 被传入但完全忽略。 lambda 自身就是 self-contained 的可调用对象。
子类 3:普通函数 / 静态函数 → QStaticSlotObject 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 template <typename Func, typename Args, typename R>struct QStaticSlotObject : public QSlotObjectBase{ Func function; static void impl (int which, QSlotObjectBase *self_, QObject *r, void **a, bool *ret) { switch (which) { case Call: call (self->function, UnpackType<Args>::unpack (a)...); break ; } } };
三、编译期选择路径:connect 模板如何决定实例化哪个子类 这是核心。我们看三种 connect 重载的模板代码(qobject.h):
成员函数指针重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 template <typename Func1, typename Func2>QMetaObject::Connection QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type) { typedef QtPrivate::FunctionPointer<Func2> SlotType; Q_STATIC_ASSERT_X (QtPrivate::HasQ_OBJECT_Macro<SlotType::Object>::Value, ...); return QObject::connectImpl ( sender, reinterpret_cast <void **>(&signal), receiver, reinterpret_cast <void **>(&slot), new QtPrivate::QSlotObject <SlotType::Object, typename SlotType::Arguments, SlotType::ReturnType>(slot), type, types, &SignalType::Object::staticMetaObject); }
Lambda 重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 template <typename Func1, typename Func2>QMetaObject::Connection QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot) { return QObject::connect ( sender, signal, sender, new QtPrivate::QFunctorSlotObject<Func2, typename QtPrivate::FunctionPointer<Func2>::Arguments, typename QtPrivate::FunctionPointer<Func2>::ReturnType>(slot), type); }
SFINAE / 重载决议 保证了:
Func2 是 void (Receiver::*)(Args...) → 匹配成员函数指针重载 → 实例化 QSlotObject
Func2 是 lambda 类型 → 匹配函数对象重载 → 实例化 QFunctorSlotObject
四、到 callSlot 层面再看一眼 回到 qobject.cpp 的 callSlot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static void callSlot (QObject *receiver, QObjectPrivate::Connection *c, void **argv) { if (c->isSlotObject) { c->slotObj->impl (QSlotObjectBase::Call, c->slotObj, receiver, argv, 0 ); } else { const QMetaObject *meta = receiver->metaObject (); int method = c->method_relative + c->method_offset; meta->static_metacall (receiver, QMetaObject::InvokeMetaMethod, method, argv); } }
callSlot 不做任何类型判断 。它只是一个转发器:永远把 receiver 塞给 impl,至于 impl 内部用不用、怎么用,那是编译期已经固化的事情。
五、用一张表总结区分机制
成员函数指针 connect
Lambda connect
模板参数 Func2
void (R::*)(Args...)
编译器生成的闭包类型
实例化的 SlotObject 子类
QSlotObject<R, Func2, ...>
QFunctorSlotObject<Func2, ...>
impl 函数指针指向
QSlotObject::impl
QFunctorSlotObject::impl
调用时对 receiver 参数的使用
static_cast<R*>(r) 转成 this,通过 ->* 调用成员函数
完全忽略 ,直接 functor(args...)
调用所需的上下文
receiver(this)+ 成员函数指针,分离存储
lambda 闭包自身已包含全部上下文
本质是策略模式 :QSlotObjectBase 是策略接口(一个手写虚表),不同子类注入不同的 impl 函数指针。编译期的模板重载决议选择了正确的子类,运行时只做一次函数指针间接调用,没有任何类型判断开销。
1 那如果是connect(sender,signal,receiver,lambda)这样子调用走的是哪个
这里的关键区别在于 C++ 模板的重载决议 :receiver 的类型是 const QObject *(一个原始指针),它不可能 被 QtPrivate::FunctionPointer 当成成员函数指针来萃取,所以不会走成员函数指针那个重载。
Qt5.8 的 qobject.h 里,这个重载签名为:
1 2 3 4 5 6 7 8 template <typename Func1, typename Func2>static inline QMetaObject::Connection connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
SFINAE / 重载决议机制 :Func2 是 lambda 类型时,QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction 为 false,因此绝不会匹配成员函数指针的重载 。同时第二个参数 const QObject *context 是一个普通指针,这跟 Func2 slot 的区别(slot 是个普通指针还是成员函数,靠 FunctionPointer::IsPointerToMemberFunction 区分)也不同——编译器没有歧义。
内部实现类似这样(伪代码,省略了类型萃取细节):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 template <typename Func1, typename Func2>QMetaObject::Connection QObject::connect ( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot, Qt::ConnectionType type) { return QObject::connectImpl ( sender, signal, context, nullptr , new QtPrivate::QFunctorSlotObject <Func2, ...>(slot), type, types, &SignalType::Object::staticMetaObject); }
而运行时的 callSlot 里:
1 2 3 4 5 6 if (c->isSlotObject) { c->slotObj->impl (QSlotObjectBase::Call, c->slotObj, receiver, argv); }
三路 Lambda connect 的 receiver 来源对照
调用形式
Connection::receiver
slotObj 子类
impl 是否用 receiver
connect(sender, signal, lambda)
sender
QFunctorSlotObject
否
connect(sender, signal, receiver, lambda)
receiver (用户传入)
QFunctorSlotObject
否
connect(sender, signal, receiver, &R::method)
receiver
QSlotObject<R, ...>
是 ,强转成 R* 当 this
关键结论:只要最后一个参数是 lambda / 仿函数,无论前面有没有传 receiver,槽对象永远是 QFunctorSlotObject,impl 永远不碰 receiver 参数。 receiver 的区别只影响后台的生命周期管理——谁析构时断开这个连接。
总结:
每一个QObject对象都有一个QObjectPrivate,而QObjectPrivate存储着一个ConnectionLists[]
1 2 3 4 5 6 7 8 9 10 11 QObject(一个具体的对象) └── QObjectPrivate └── connectionLists (QObjectConnectionListVector) ├── [0] ConnectionList → Connection → Connection → ... ├── [1] ConnectionList → Connection → ... ├── [2] ConnectionList → NULL ├── [3] ConnectionList → Connection → ... └── ... 每个 ConnectionList 对应一个信号索引(signal_index) 每个 Connection 是一个链表节点,记录了一对 sender.signal-receiver.slot 的连接关系
ConnectionLists[]存储Connection*指针,对应的就是该对象的当前信号signal的所有connection
connect()函数做的就是制作signal和sigal的connection然后把它插入到对应signal的ConnectionList中
旧式connect(SIGNAL(),SLOT())信号被激活时需要用到控件的QMetaObject中类似函数表的东西,通过函数的索引值得到函数的地址然后去调用槽函数
新式connect(&成员函数地址)和connect(lambda)就需要在connection中添加一个是否为SlotObject的选项,然后把槽函数包装成函数对象存储在connection中。信号被激活时就会直接调用这个槽函数对象。
信号signal函数由MOC来生成,它会同意调用activate()激活函数。这个激活函数内部做的就是遍历 信号signal下标对应的ConnectionList,把链表 中每个结点里的槽函数 都调用一遍。