Qt QObject

1.测试代码如下:

  #include<QApplication>
  #include<QPushButton>
  #include<QDebug>
  using namespace std;
  
  int main(int argc, char *argv[])
  {
      QApplication app(argc, argv);
      int nSize = sizeof(QObject);
     qDebug() << nSize << endl; // 8
     QPushButton* pQuit = new QPushButton("Quit");
     delete pQuit;
     return app.exec();
 }

QObject是Qt类体系的唯一基类,重要性就像MFC中的CObject或Delphi中的TObject,是Qt各种功能的活水源头。此句代码:

     int nSize = sizeof(QObject);
     qDebug() << nSize << endl; // 8

QObject的大小是8,除了虚函数表(即所谓的虚表)指针需要4个字节以外,另外的4个字节是指d_ptr(指针成员变量:QObjectData *d_ptr;)

备注:在最新版本中,被替换为QScopedPointer<QObjectData> d_ptr;

那么,QObjectData是个什么鬼?且往下看:

经分析,QObject类的数据成员被封装在QObjectData类中了,为什么要如此封装数据呢?

原因简述:Qt中有一个很重要的设计模式,句柄(方法)—实体(数据)模式,也就是以QObject为基类的类一般都是句柄类,一般会有一个指针指向一个实体类(数据成员类),在实体类中保存全部的数据成员。而且,一般情况下这个指针还是受保护成员变量,方便其子类的使用。因此,也可以说,与句柄类继承关系平行的也有一套实体类派生体系。所以,准确的说,Qt的基类其实有两个:一个是QObject,这是句柄类的唯一基类;另一个是QObjectData,这是实体类的基类。

本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓

QObjectData类定义如下:

  class Q_CORE_EXPORT QObjectData
  {
  public:
      virtual ~QObjectData() = 0;
      QObject *q_ptr;
      QObject *parent;
      QObjectList children;
  
      uint isWidget : 1;
     uint blockSig : 1;
     uint wasDeleted : 1;
     uint isDeletingChildren : 1;
     uint sendChildEvents : 1;
     uint receiveChildEvents : 1;
     uint isWindow : 1; //for QWindow
     uint unused : 25;
     int postedEvents;
     QDynamicMetaObjectData *metaObject;
     QMetaObject *dynamicMetaObject() const;
 };

QObject *q_ptr; 这个指针指向此实体类对应的句柄类,此指针与上面的指针QScopedPointer<QObjectData> d_ptr; 遥相呼应,使得句柄类和实体类可以双向的引用,为什么是这样的命名方式呢?可能q指的是Qt接口类,d指的是Data实体类。当然这是猜测,但是或许可以方便你记忆。在Qt中,这两个指针名字是非常重要的,必须记住(嗯哼?为什么重要呢?请看下面两个宏的定义)。

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

  template <typename T>
  static inline T *qGetPtrHelper(T *ptr) { return ptr; }
  
  #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;
  
  #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文件中调用的方便,更是直接声明了以下两个宏:

1 #define Q_D(Class) Class##Private * const d = d_func()
2 #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 blockSig : 1;
     uint wasDeleted : 1;
     uint isDeletingChildren : 1;
     uint sendChildEvents : 1;
     uint receiveChildEvents : 1;
     uint isWindow : 1; //for QWindow
     uint unused : 25;

这些代码就简单了,主要是一些标记位,为了节省内存开销,这里采用了位域的语法,还保留了25位为unused,留做以后的扩充。

具体还是看一个例子吧!对这种句柄实体模式加深认识,这就是Qt中的按钮类QPushButton,QPushButton的句柄类派生关系以及QPushButtonPrivate的实体类派生关系是:

可以看出,这里确实是一个平行体系,只不过实体类派生关系中多了一个QObjectPrivate,这个类封装了线程处理,信号和槽机制等具体的实现,可以说它才是Qt实体类中真正起作用的基类,而QObjectData不过是一层浅浅的数据封装而已。先不忙了解QObjectPrivate类中的接口和实现,我们先看看在Qt中,句柄类和实体类这两条体系是如何构造的?

QPushButton* pQuit = new QPushButton("Quit"); 创建一个Qt的按钮,简简单单一行代码,其实背后大有玄机,请看如下代码:

  QObject::QObject(QObjectPrivate &dd, QObject *parent)
      : d_ptr(&dd)
  {
      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;
             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);
             }
         }
         QT_CATCH(...)
         {
             d->threadData->deref();
             QT_RETHROW;
         }
     }
     qt_addObject(this);
 }
 
 QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
     : QObject(dd, 0), QPaintDevice()
 {
     Q_D(QWidget);
     QT_TRY
     {
         d->init(parent, f);
     }
     QT_CATCH(...)
     {
         QWidgetExceptionCleaner::cleanup(this, d_func());
         QT_RETHROW;
     }
 }
 
 QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
     : QWidget(dd, parent, 0)
 {
     Q_D(QAbstractButton);
     d->init();
 }
 
 QPushButton::QPushButton(const QString &text, QWidget *parent)
     : QAbstractButton(*new QPushButtonPrivate, parent)
 {
     Q_D(QPushButton);
     setText(text);
     d->init();
 }

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

QAbstractButton的构造函数中再调用基类QWidget的构造函数,同时把QPushButtonPrivate实体类对象传给基类

QWidget继续做着同样的事情

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

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

  QObject::QObject(QObjectPrivate &dd, QObject *parent)
      : d_ptr(&
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值