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(&currentData->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;

  

QT源码分析:QObject的更多相关文章

  1. Qt源码分析之QObject

    原文:http://blog.csdn.net/oowgsoo/article/details/1529284 我感觉oowgsoo兄弟写的分析相当透彻,赞! 1.试验代码: #include < ...

  2. QT源码分析(从QApplication开始)

    QT源码分析 转载自:http://no001.blog.51cto.com/1142339/282130 今天,在给同学讲东西的时候,谈到了Qt源代码的问题,才发现自己对Qt机制的了解是在太少了,而 ...

  3. QT源码分析:QTcpServer

    最近在看有关IO复用方面的内容,自己也用标准c++库实现了select模型.iocp模型.poll模型.回过头来很想了解QT的socket是基于什么模型来实现的,所以看了QT关于TcpServer实现 ...

  4. Qt源码分析之信号和槽机制

    Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个 ...

  5. Qt源码分析之信号和槽机制(QMetaObject是一个内部struct)

    Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个 ...

  6. Qt源码分析之QPointer

    QPointer是一个指针封装类,其作用类似于智能指针,但是它最大的特点应该是在指针的控制上,它希望一个Qt的指针(当然是从QObject派生的)可以同时被多个类拥有,这在界面编程中当然是很常见的事情 ...

  7. QT 源码分析--1

    Ref: http://blog.sina.com.cn/s/blog_6e80f1390100qoc0.html 安装qt之后(我使用的是online自动安装),安装目录下有\5.10.1\Src\ ...

  8. Qt update刷新之源码分析总结

    大家好,我是IT文艺男,来自一线大厂的一线程序员 经过前面几次的Qt源码讲解,我相信大家对Qt update刷新机制从底层原理上有了一个深刻的理解:这次做一个收尾总结,来复盘前面几次所讲解的内容: 分 ...

  9. Qt之使用setWindowFlags方法遇到的问题(追踪进入QWidget的源码分析原因,最后用WINAPI解决问题)good

    一.简述 前段时间在使用setWindowFlags方法时遇到了一个坑,具体情况是想通过窗口界面上一个checkBox来控制窗口当前状态是否置顶,而Qt提供了Qt::WindowStaysOnTopH ...

随机推荐

  1. js的对象创建

    创建对象 字面量的方式: var myHonda = {color: "red", wheels: 4, engine: {cylinders: 4, size: 2.2}} 就是 ...

  2. foreach中的collection

    foreach中collection的三种用法 https://www.cnblogs.com/xiemingjun/p/9800999.html foreach的主要用在构建in条件中,它可以在SQ ...

  3. webpack资源管理

    一.概况 ①webpack不仅可以打包JavaScript模块,甚至它把网页开发中的一切资源的都可以当作模块来打包处理 ②但是webpack本身不支持,它只是一个打包平台,其他资源,例如css.les ...

  4. Vue与REACT两个框架的区别和优势对比

    VUE和REACT两个JavaScript框架都是当下比较受欢迎的,他们两者之间的区别有那些,各自的优缺点是什么,本文将为你呈现. 简单介绍 除非你最近一直不关注前端的发展,不然你肯定听说过由Face ...

  5. pm2 启动模式 fork 和 cluster 的区别

    fork模式,单实例多进程,常用于多语言混编,比如php.python等,不支持端口复用,需要自己做应用的端口分配和负载均衡的子进程业务代码. 缺点就是单服务器实例容易由于异常会导致服务器实例崩溃. ...

  6. Kafka(三)High Availability 高可用

    参考文档: http://www.jasongj.com/2015/04/24/KafkaColumn2/#ACK%E5%89%8D%E9%9C%80%E8%A6%81%E4%BF%9D%E8%AF% ...

  7. @Autowired和@Resource的区别和联系

    背景: 今天下班路上看到一个大货车,于是想到了装配,然后脑海里跳出了一个注解@Autowired(自动装配),于是又想到最近工作项目用的都是@Resource注解来进行装配.于是本着学什么东西都要一钻 ...

  8. What is the difference between UNION and UNION ALL?

    What is the difference between UNION and UNION ALL? UNION removes duplicate records (where all colum ...

  9. Python的threading和multiprocessing

    Python的threading 基础用法, 通过 threading.Thread() 创建线程, 然后 start() 和 join() import time import threading ...

  10. tp使用ajaxReturn返回二维数组格式的字符串,前台如何获取非乱码

    参考: https://www.cnblogs.com/jiqing9006/p/5000849.html https://blog.csdn.net/zengxiangxuan123456/arti ...