QT 框架里面最大的特色就是在 C++ 的基础上增加了元对象系统 (Meta-Object System), 而元对象系统里面最重要的内容就是信号与槽机制, 这个机制是在 C++ 语法的基础上实现的, 使用了函数, 函数指针, 回调函数等概念. 当然与我们自己去写函数所不同的是槽与信号机制会自动帮我们生成部分代码, 比如我们写的信号函数就不需要写它的实现部分, 这是因为在我们编译程序的时候, 编译器会自动生成这一部分代码, 当我们调用 connect 函数的时候, 系统会自动将信号函数与槽函数相连接, 于是当我们调用信号函数的时候, 系统就会自动回调槽函数, 不管你是在同一线程下调用或者在不同线程下调用, 系统都会自动评估, 并在合理的时候触发函数, 以此来保证线程的安全. 信号与槽机制是线程安全的, 这可以使得我们在调用的时候不用再额外的增加过多保证线程同步的代码, 为了实现元对象系统, QT 把所有相关实现写在了 QObject 类中, 所以当你想使用元对象系统的时候, 你所写的类需要继承自 QObject, 包括 QT 自带的所有类都是继承自 QObject, 所以分析 QObject 的代码, 对了解 QT 的元对象机制有非常大的帮助, 我并不打算把 QObject 类的每一行代码都写下来, 只想把其中比较关键的内容或者对分析 QT 源码有帮助的内容介绍一下.
1. 宏 Q_OBJECT
这个宏展开以后是如下定义:
- #define Q_OBJECT \
- public: \
- QT_WARNING_PUSH \
- Q_OBJECT_NO_OVERRIDE_WARNING \
- static const QMetaObject staticMetaObject; \
- virtual const QMetaObject *metaObject() const; \
- virtual void *qt_metacast(const char *); \
- virtual int qt_metacall(QMetaObject::Call, int, void **); \
- QT_TR_FUNCTIONS \
- private: \
- Q_OBJECT_NO_ATTRIBUTES_WARNING \
- Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
- QT_WARNING_POP \
- struct QPrivateSignal {}; \
- QT_ANNOTATE_CLASS(qt_qobject, "")
你可以看到这个宏定义了一些函数, 并且函数名都带有 meta, 所以不难猜到这些函数和 QT 的元对象系统是有关系的, 实际上你在 qobject.cpp 里面是找不到这些函数的实现的, 它们的实现都在 moc_qobject.cpp 里面. QT 的元对象系统是这样处理的, 当你编译你的工程时, 它会去遍历所有 C++ 文件, 当发现某一个类的私有部分有声明 Q_OBJECT 这个宏时, 就会自动生成一个 moc_*.cpp 的文件, 这个文件会生成信号的实现函数, Q_OBJECT 宏里面定义的那些函数也会在这个文件里面实现, 并生成与类相关的元对象. 这就是为什么我们定义一个信号的时候, 不需要实现它, 因为它的实现已经写在 moc_*.cpp 文件里面了.
2.Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
这个宏是定义一个属性, 属性也是元对象系统的内容之一, 实际上我们在做界面设计的时候经常会用到属性, 比如修改 Label 的显示内容, 需要用到 Text 属性, 修改窗体长宽等等, 在你做界面设计的时候, 属性编辑框里面所显示的就是当前对象的所有属性, 而这些属性的定义就是用上面的宏来定义的. 实际上属性和变量是有点相似的, 都是读值和写值的功能, 那为什么不直接对变量操作就好了? 虽然看起来相似, 但是还是有不同点, 第一属性可以定义为可读写的, 也可以定义为只读的, 比如有些数据我们只在类的内部做修改不允许在外部做修改, 但是有时候又需要在外部查看这个值, 就可以设置为只读属性, 而变量是做不到这点的, 你把变量放在 public 部分, 那么这个变量就可以在任何地方被修改, 这就破坏了类的封装性. 第二属性可以定义信号, 当属性变化的时候触发信号, 这样我们可以在信号触发时做一些工作, 比如当你设置 LineEdit 为 readonly 时, 你会发现输入框的背景颜色被改变了, 这就可以通过属性变化的信号来处理.
3.Q_DECLARE_PRIVATE(QObject)
这个宏的定义如下:
- #define Q_DECLARE_PRIVATE(Class) \
- inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
- inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
- friend class Class##Private;
这个宏首先创建了两个内联函数, 返回值都是 QObjectPrivate *, 并且声明 QObjectPrivate 为友元类, QObjectPrivate 这个类是在 qobject_p.h 中定义, 它继承至 QObjectData, 你可以看到 d_func() 是将 d_prt 强制转换为 QObjectPrivate * 类型, 而 d_prt 这个指针在 QObject 里面定义的是 QObjectData 的指针类型, 所以这里可以进行强转, QObjectPrivate 这个类主要存放 QOject 类需要用到的一些子对象, 变量等. 为什么要介绍这个宏, 如果你有看 QT 源码习惯的话, 你会发现几乎每一个类都用到了这个宏, 我们自己写的类会经常把类内部用的变量声明在 private 部分, 但是 QT 源码并不是这样做的, 它的做法是给每个类创建一个以类名 + Private 的类, 例如 QObject 对应的就是 QObjectPrivate, 这个类实际上就是用来存放 QObject 需要用到的所有私有变量和私有对象, 而 QObject 更多的是函数实现, 你去看其他的源码也是如此, 子对象声明在 Q*Private 中, 而本类只实现函数.
4. 构造函数
- QObject::QObject(QObject *parent)
- : d_ptr(new QObjectPrivate)
- {
- Q_D(QObject);
- d_ptr->q_ptr = this;
- d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
- d->threadData->ref();
- if (parent) {
- QT_TRY {
- if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
- parent = 0;
- setParent(parent);
- } QT_CATCH(...) {
- d->threadData->deref();
- QT_RETHROW;
- }
- }
- #if QT_VERSION <0x60000
- qt_addObject(this);
- #endif
- if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
- reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);
- }
(1) 首先第一步就创建 d_ptr 指针.
(2)Q_D(QObject); 这个宏你可以在 QT 的很多源码里面看到. 它展开以后是下面的样子:#define Q_D(Class) Class##Private * const d = d_func();
d_fun() 函数前面讲到了, 其实就是返回 d_ptr 了. 所以这个宏的意思是定义一个指针 d 指向 d_ptr;
(3)d_ptr->q_ptr = this;
q_ptr 是 QOject 类型, 这里把 this 指针赋给了它, 所以使得 QObjectPrivate 可以回调 QOject 的函数.
(4) 初始化 threadData
5.moveToThread
(1) 如果要移动的线程和 Object 本身就是同一线程, 那么直接返回
- Q_D(QObject);
- if (d->threadData->thread == targetThread) {
- // object is already in this thread
- return;
- }
(2) 如果 parent 不为空, 不允许移动到其他线程, 子类必需与父类在同一线程.
- if (d->parent != 0) {
- qWarning("QObject::moveToThread: Cannot move objects with a parent");
- return;
- }
(3) 如果对象是窗体类, 不允许移动到线程, 窗体类必需在主线程运行, 在子线程去直接调用窗体控件都是不安全的, 可能导致程序崩溃, 合理的做法是通过信号槽机制.
- if (d->isWidget) {
- qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
- return;
- }
(4) 只有在对象所在线程才能将对象移动到另一个线程, 不能在其他线程将对象移动到某个线程, 这种操作是不被允许的.
- QThreadData *currentData = QThreadData::current();
- QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : Q_NULLPTR;
- if (d->threadData->thread == 0 && currentData == targetData) {
- // one exception to the rule: we allow moving objects with no thread affinity to the current thread
- currentData = d->threadData;
- } else if (d->threadData != currentData) {
- qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p).\n""Cannot move to target thread (%p)\n",
- currentData->thread.load(), d->threadData->thread.load(), targetData ? targetData->thread.load() : Q_NULLPTR);
- #ifdef Q_OS_MAC
- qWarning("You might be loading two sets of Qt binaries into the same process."
- "Check that all plugins are compiled against the right Qt binaries. Export"
- "DYLD_PRINT_LIBRARIES=1 and check that only one set of binaries are being loaded.");
- #endif
- return;
- }
(5) 线程转移
- // 为转移线程准备, 遍历所有子对象, 并给每一个子对象发送一个 QEvent::ThreadChange 的事件.
- d->moveToThread_helper();
- if (!targetData)
- targetData = new QThreadData(0);
- // 为转移事件上锁
- QOrderedMutexLocker locker(¤tData->postEventList.mutex,
- &targetData->postEventList.mutex);
- currentData->ref();
- // 遍历所有子对象及自身, 将 currentData 的 postEventList 里面的对象转移到 targetData, 将所有子对象及自身的 threadData 设置为 targetData
- d_func()->setThreadData_helper(currentData, targetData);
- locker.unlock();
- currentData->deref();
6.connect 函数
connet 的重构函数很多, 这里选择其中一个来分析.
- QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal,
- const QObject *receiver, const QMetaMethod &method,
- Qt::ConnectionType type)
(1) 首选 sender,receiver 不能为空, signal 必须是 Signal 类型, 也就是声明在 signals: 下面, method 不能为构造函数, 不满足这几个条件则返回.
- if (sender == 0
- || receiver == 0
- || signal.methodType() != QMetaMethod::Signal
- || method.methodType() == QMetaMethod::Constructor) {
- qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
- sender ? sender->metaObject()->className() : "(null)",
- signal.methodSignature().constData(),
- receiver ? receiver->metaObject()->className() : "(null)",
- method.methodSignature().constData() );
- return QMetaObject::Connection(0);
- }
(2) 检查 signal 和 method 是否真实存在, 在编译期即使传入的信号不存在也不会报错, 在运行期会检查是否存在, 所以在写 connect 函数的时候要仔细检查, 尽量使用 & ClassName::functionName 的方式让系统自动补全, 当然也可以通过 connect 的返回值来判断调用是否成功, 如调用不成功则抛出异常.
- int signal_index;
- int method_index;
- {
- int dummy;
- QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
- QMetaObjectPrivate::memberIndexes(receiver, method, &dummy, &method_index);
- }
- const QMetaObject *smeta = sender->metaObject();
- const QMetaObject *rmeta = receiver->metaObject();
- if (signal_index == -1) {
- qWarning("QObject::connect: Can't find signal %s on instance of class %s",
- signal.methodSignature().constData(), smeta->className());
- return QMetaObject::Connection(0);
- }
- if (method_index == -1) {
- qWarning("QObject::connect: Can't find method %s on instance of class %s",
- method.methodSignature().constData(), rmeta->className());
- return QMetaObject::Connection(0);
- }
(3) 检查 signal 和 method 的参数个数和类型是否是一致的, 不一致则返回.
- if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
- qWarning("QObject::connect: Incompatible sender/receiver arguments"
- "\n %s::%s --> %s::%s",
- smeta->className(), signal.methodSignature().constData(),
- rmeta->className(), method.methodSignature().constData());
- return QMetaObject::Connection(0);
- }
(4) 如果你设置的连接方式为 QueuedConnection, 那么所有的参数都必须是元数据类型, 自定义的类型, 如自定义的结构体或枚举必须注册为元数据类型, 否则无法作为信号和槽的参数, 因为最终要将这些参数加入到消息队列里面.
- int *types = 0;
- if ((type == Qt::QueuedConnection)
- && !(types = queuedConnectionTypes(signal.parameterTypes())))
- return QMetaObject::Connection(0);
(5) 所有的检查完毕, 调用 QMetaObject 的 Connection 函数, 而 QMetaObject 的 Connection 会创建一个 Connection 的对象, 这个对象会保存信号和槽的函数对象, 然后会把这个 Connection 对象保存到 sender 的一个数组中, 当你触发信号的时候, 因为 Connection 对象保存在了 sender 中, 所以可以找到原来绑定的槽函数, 然后回调槽函数.
- QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
- sender, signal_index, signal.enclosingMetaObject(), receiver, method_index, 0, type, types));
- return handle;
来源: https://www.cnblogs.com/WushiShengFei/p/9820835.html