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