Qt源码分析之QObject(转自CSDN,作者oowgsoo)

1.试验代码:

#include <QApplication>#include <QtCore>#include <QtGui>

int main(int argc, char *argv[]){ QApplication app(argc, argv);

 int size = sizeof(QObject);

 QPushButton* quit = new QPushButton("Quit"); delete quit;

 return app.exec();}

QObject是Qt类体系的唯一基类,就象MFC中的CObject和Dephi中的TObject,是Qt各种功能的源头活水,因此Qt源码分析的第一节就放在这个QObject上

int size = sizeof(QObject);

QObject的大小是8,除了虚函数表指针需要的4个字节以外,另外的4个字节是:
    QObjectData *d_ptr;
QObject中的数据被封装在QObjectData类中了,为什么要封装数据呢?
原因是Qt中有一个很重要的设计模式就是句柄实体模式,也就是以QObject为基类的类一般都是句柄类,一般只有一个指针指向一个实体类,在实体类中保存全部的数据
而且一般情况下这个指针还是私有的,方便以后修改句柄类的实现细节
因此,也可以说和句柄类继承关系平行的也有一套实体类派生体系,因此,准确的说,Qt的基类其实有两个,一个是QObject,这是句柄类的唯一基类,另一个是QObjectData,这是实体
类的基类

QObjectData类定义如下:

class QObjectData {public:    virtual ~QObjectData() = 0;    QObject *q_ptr;    QObject *parent;    QObjectList children;

    uint isWidget : 1;    uint pendTimer : 1;    uint blockSig : 1;    uint wasDeleted : 1;    uint ownObjectName : 1;    uint sendChildEvents : 1;    uint receiveChildEvents : 1;    uint unused : 25;    int postedEvents;#ifdef QT3_SUPPORT    int postedChildInsertedEvents;#else    int reserved;#endif};

QObject *q_ptr;
这个指针指向实体类对应的句柄类,这和上面的代码
QObjectData *d_ptr;
遥相呼应,使得句柄类和实体类可以双向的引用,为什么是这样的命名方式呢?可能q指的是Qt接口类,d指的是Data数据类,这当然是猜测了,但是或许可以方便你记忆,在Qt中,
这两个指针名字是非常重要的,必须记住

但是仅仅如此还是不容易使用这两个指针,因为它们都是基类的类型,难道每次使用都要类型转换吗?为了简单起见,Qt在这里声明了两个宏

#define Q_DECLARE_PRIVATE(Class) /    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } /    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } /    friend class Class##Private;

#define Q_DECLARE_PUBLIC(Class) /    inline Class* q_func() { return static_cast<Class *>(q_ptr); } /    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } /    friend class Class;

只要在类的头文件中使用这两个宏,就可以通过函数直接得到实体类和句柄类的实际类型了,而且这里还声明了友元,使得数据类和句柄类连访问权限也不用顾忌了

而且为了cpp文件中调用的方便,更是直接声明了以下两个宏

#define Q_D(Class) Class##Private * const d = d_func()#define Q_Q(Class) Class * const q = q_func()

好了,使用起来倒是方便了,但是以后局部变量可千万不能声明为d和q了

这里的d_func和q_func函数是非常常用的函数,可以理解为一个是得到数据类,一个是得到Qt接口类

QObject *parent;
这里指向QObject的父类
QObjectList children;
这里指向QObject相关的子类列表
这确实是个大胆的设计,如果系统中产生了1000000个QObject实例(对于大的系统,这个数字很容易达到吧),每个QObject子类平均下来是100(这个数字可能大了),
光这些指针的开销就有1000000*100*4=400M,是够恐怖的,如果我们必须在灵活性和运行开销之间做一个选择的话,无疑Qt选择了前者,对此我也很难评论其中的优劣,
还是祈求越来越强的硬件水平和Qt这么多年来得到的赫赫威名保佑我们根本就没有这个问题吧,呵呵
总之,Qt确实在内存中保存了所有类实例的树型结构

uint isWidget : 1;
    uint pendTimer : 1;
    uint blockSig : 1;
    uint wasDeleted : 1;
    uint ownObjectName : 1;
    uint sendChildEvents : 1;
    uint receiveChildEvents : 1;
    uint unused : 25;
这些代码就简单了,主要是一些标记位,为了节省内存开销,这里采用了位域的语法,还保留了25位为unused,留做以后的扩充

#ifdef QT3_SUPPORT    int postedChildInsertedEvents;#else    int reserved;#endif

这里或许是为了兼容Qt3下序列化的数据吧,即使没有定义QT3_SUPPORT,还是保留了一个数据reserved,以保证整个QObjectData的大小不变

具体看一个例子吧,对这种句柄实体模式加深认识,这就是Qt中的按钮类QPushButton
QPushButton的句柄类派生关系是:
QObject
 QWidget
  QAbstractButton
   QPushButton
   
QPushButton的实体类派生关系是:
QObjectData
 QObjectPrivate
  QWidgetPrivate
   QAbstractButtonPrivate
    QPushButtonPrivate

可以看出,这里确实是一个平行体系,只不过实体类派生关系中多了一个QObjectPrivate,这个类封装了线程处理,信号和槽机制等具体的实现,可以说它才是Qt实体类中
真正起作用的基类,而QObjectData不过是一层浅浅的数据封装而已

先不忙了解QObjectPrivate类中的接口和实现,我们先看看在Qt中,句柄类和实体类这两条体系是如何构造的?

QPushButton* quit = new QPushButton("Quit");

创建一个Qt的按钮,简简单单一行代码,其实背后大有玄机

QPushButton::QPushButton(const QString &text, QWidget *parent): QAbstractButton(*new QPushButtonPrivate, parent)

首先QPushButton的构造函数中调用了QAbstractButton的构造函数,同时马上new出来一个QPushButtonPrivate实体类,然后把指针转换为引用传递给QAbstractButton

QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent): QWidget(dd, parent, 0)

QAbstractButton的构造函数中继续调用基类QWidget的构造函数,同时把QPushButtonPrivate实体类指针继续传给基类

QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WFlags f): QObject(dd, ((parent && (parent->windowType() == Qt::Desktop)) ? 0 : parent)),QPaintDevice()

QWidget继续坐着同样的事情

QObject::QObject(QObjectPrivate &dd, QObject *parent): d_ptr(&dd)

终于到了基类QObject,这里就直接把QPushButtonPrivate的指针赋值给了d_ptr(还记得这个变量名称吧)

最终在QPushButton构造时同时产生的new QPushButtonPrivate被写到了QObject中的d_ptr中

QObject::QObject(QObjectPrivate &dd, QObject *parent)    : d_ptr(&dd){    Q_D(QObject);    ::qt_addObject(d_ptr->q_ptr = this);    QThread *currentThread = QThread::currentThread();    d->thread = currentThread ? QThreadData::get(currentThread)->id : -1;    Q_ASSERT_X(!parent || parent->d_func()->thread == d->thread, "QObject::QObject()",               "Cannot create children for a parent that is in a different thread.");    if (parent && parent->d_func()->thread != d->thread)        parent = 0;    if (d->isWidget) {        if (parent) {            d->parent = parent;            d->parent->d_func()->children.append(this);        }        // no events sent here, this is done at the end of the QWidget constructor    } else {        setParent(parent);    }}

然后执行QObject的构造函数,这里主要是一些线程的处理,先不理它

QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WFlags f)    : QObject(dd, ((parent && (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice(){    d_func()->init((parent && parent->windowType() == Qt::Desktop ? parent : 0), f);}

然后是QWidget的构造函数,这里调用了数据类QWidgetPrivate的init函数,这个函数不是虚函数,因此静态解析成QWidgetPrivate的init函数调用

QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)    : QWidget(dd, parent, 0){    Q_D(QAbstractButton);    d->init();}

然后是QAbstractButton的构造函数,这里调用了数据类QAbstractButton的init函数,这个函数不是虚函数,因此静态解析成QAbstractButton的init函数调用

QPushButton::QPushButton(const QString &text, QWidget *parent)    : QAbstractButton(*new QPushButtonPrivate, parent){    Q_D(QPushButton);    d->init();    setText(text);}

然后是QPushButton的构造函数,这里调用了数据类QPushButton的init函数,这个函数不是虚函数,因此静态解析成QPushButton的init函数调用

现在的事情很清楚了,总结一下:
QPushButton在构造的时候同时生成了QPushButtonPrivate指针,QPushButtonPrivate创建时依次调用数据类基类的构造函数
QPushButton的构造函数中显示的调用了基类的构造函数并把QPushButtonPrivate指针传递过去,QPushButton创建时依次调用接口类基类的构造函数
在接口类的构造函数中调用了平行数据类的init函数,因为这个函数不是虚函数,因此就就是此次调用了数据类的init函数

需要指出的是,为什么QPushButtonPrivate实体类指针要转换为引用呢?为什么不是直接传递指针?结论是人家喜欢这样写,就是不传指针传引用,而且要用一个*new之类的怪异语法,
真叫人没有办法,其实这里用指针是一样的,代码看起来也自然一些.

delete quit;
说完了构造,再说说析构

QPushButton::~QPushButton(){}

这里当然会调用QPushButton的析构函数了

QAbstractButton::~QAbstractButton(){#ifndef QT_NO_BUTTONGROUP    Q_D(QAbstractButton);    if (d->group)        d->group->removeButton(this);#endif}

然后是QAbstractButton的析构函数

QWidget::~QWidget(){    Q_D(QWidget);...}

然后是QWidget的析构函数,这里洋洋洒洒一大堆代码,先不管它

QObject::~QObject(){...}

最后是QObject的析构函数,这里也是洋洋洒洒的一大堆

    Q_D(QObject);    if (d->wasDeleted) {#if defined(QT_DEBUG)        qWarning("Double QObject deletion detected");#endif        return;    }    d->wasDeleted = true;

这些没有什么好说的,就是设一个wasDeleted的标志,防止再被引用,对于单线程情况下,马上就要被删除了,还搞什么标记啊,根本没用,但是对于多线程情况下,这个标记应该是有用的

   // set all QPointers for this object to zero    GuardHash *hash = ::guardHash();    if (hash) {        QWriteLocker locker(guardHashLock());        GuardHash::iterator it = hash->find(this);        const GuardHash::iterator end = hash->end();        while (it.key() == this && it != end) {            *it.value() = 0;            it = hash->erase(it);        }    }

这里是支持QPointers的实现代码,我们以后再说

emit destroyed(this);

Qt的一个指针删除时要发送destroyed信号,一般情况下是没有槽来响应的

QConnectionList *list = ::connectionList();    if (list) {        QWriteLocker locker(&list->lock);        list->remove(this);    }

这里清除了信号槽机制中的记录

   if (d->pendTimer) {        // have pending timers        QThread *thr = thread();        if (thr || d->thread == 0) {            // don‘t unregister timers in the wrong thread            QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance(thr);            if (eventDispatcher)                eventDispatcher->unregisterTimers(this);        }    }

这里清除定时器

d->eventFilters.clear();

这里清除事件过滤机制

    // delete children objects    if (!d->children.isEmpty()) {        qDeleteAll(d->children);        d->children.clear();    }

这里清除所有子类指针,当然每个子类指针清除时又会清除它的所有子类,因此Qt中new出来的指针很少有显示对应的delete,因为只要最上面的指针被框架删除了,
它所连带的所有子类都被自动删除了

   {        QWriteLocker locker(QObjectPrivate::readWriteLock());        ::qt_removeObject(this);

        /*          theoretically, we cannot check d->postedEvents without          holding the postEventList.mutex for the object‘s thread,          but since we hold the QObjectPrivate::readWriteLock(),          nothing can go into QCoreApplication::postEvent(), which          effectively means noone can post new events, which is what          we are trying to prevent. this means we can safely check          d->postedEvents, since we are fairly sure it will not          change (it could, but only by decreasing, i.e. removing          posted events from a differebnt thread)        */        if (d->postedEvents > 0)            QCoreApplication::removePostedEvents(this);    }

    if (d->parent)        // remove it from parent object        d->setParent_helper(0);

    delete d;    d_ptr = 0;

时间: 2024-10-08 06:48:19

Qt源码分析之QObject(转自CSDN,作者oowgsoo)的相关文章

QT源码分析:QTcpServer

最近在看有关IO复用方面的内容,自己也用标准c++库实现了select模型.iocp模型.poll模型.回过头来很想了解QT的socket是基于什么模型来实现的,所以看了QT关于TcpServer实现的相关源码,现在将所了解的内容记录下来,希望对感兴趣的朋友有所帮助. 1.我们先从QTcpServer的构造函数来看,下面是QTcpServer的构造函数原型: QTcpServer::QTcpServer(QObject *parent) : QObject(*new QTcpServerPriv

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

Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用多了,也复杂多了 MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数开销太大.在Qt中也没有采用C++中的虚函数机制,原因与此相同.其实这里还有更深层次上的原

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

Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用多了,也复杂多了 MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数开销太大.在Qt中也没有采用C++中的虚函数机制,原因与此相同.其实这里还有更深层次上的原

cocos2d-x 源码分析 : control 源码分析 ( 控制类组件 controlButton)

源码版本来自3.1rc 转载请注明 cocos2d-x源码分析总目录 http://blog.csdn.net/u011225840/article/details/31743129 1.继承结构 control的设计整体感觉挺美的,在父类control定义了整个控制事件的基础以及管理,虽然其继承了Layer,但其本身和UI组件的实现并没有关联.在子类(controlButton,controlSwitch,controlStepper等中实现不同的UI组件).下面通过源码来分析control与

仿网易新闻导航栏PagerSlidingTabStrip源码分析

转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持!   前言 最近工作比较忙,所以现在才更新博文,对不住大家了~!言归正传,我们来说说这个PagerSlidingTabStrip,它是配合ViewPager使用的导航栏,网易新闻就是用的这个导航,我们仔细观察这个导航栏不仅他是跟着ViewPager滑动而滑动,而且指示器还会随着标题的长度而动态的变化长度. · 下载地址: Github:https://github.com/astuet

cocos2d-x 源码分析 : EventDispatcher、EventListener、Event 源码分析 (新触摸机制,新的NotificationCenter机制)

源码版本来自3.x,转载请注明 cocos2d-x 源码分析总目录 http://blog.csdn.net/u011225840/article/details/31743129 1.继承结构 1.1 结构 不详吐槽太多,也不贴图了,贴图要审核好久好久好久好久. 从小到大,先来看下Event的结构. 1.Event--------EventTouch,EventCustom,EventMouse,EventKeyboard,EventFocus,EventAcceleration 其中,Eve

android-smart-image-view源码分析

SmartImageView源码分析 一.描述 目前Android应用开发涌出了各种各样出自大牛之手的成熟稳定的开源库,供开发者使用,虽然很明显的提高了App的开发效率,也同样凸显出部分问题: 我只知道如何去使用(也许还并不能随心所欲的使用,只知道简单的使用方式),并不清楚内部实现原理. 出了问题解决不了,冒出想法:这玩意真伤脑筋,怎么老出问题.好慌 ~ 相信用过一些开源库的同志都遇到过类似揪心的问题.开源库不是自己写的,也不清楚里面是啥原理,遇到问题无从下手解决显然也是很正常的事情.不过从另一

Java 序列化和反序列化(三)Serializable 源码分析 - 2

目录 Java 序列化和反序列化(三)Serializable 源码分析 - 2 1. ObjectStreamField 1.1 数据结构 1.2 构造函数 2. ObjectStreamClass Java 序列化和反序列化(三)Serializable 源码分析 - 2 在上一篇文章中围绕 ObjectOutputStream#writeObject 讲解了一下序列化的整个流程,这中间很多地方涉及到了 ObjectStreamClass 和 ObjectStreamField 这两个类.

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

一.简述 前段时间在使用setWindowFlags方法时遇到了一个坑,具体情况是想通过窗口界面上一个checkBox来控制窗口当前状态是否置顶,而Qt提供了Qt::WindowStaysOnTopHint标志,能够让窗口始终保持在其他窗口前端,也就是将窗口置顶. 理论上,我们在勾选上checkBox之后将Qt::WindowStaysOnTopHint标志设置上,就会将窗口置顶,结果却将窗口隐藏了.那么为什么第二次调用setWindowFlags设置窗口标志位时窗口会隐藏了呢(实际上调用了hi