QObject源码分析

QObject是Qt的基类

QObject object;
qDebug() << sizeof (object);

QObject的大小是8,除了虚函数表指针需要的4个字节以外,另外的4个字节是QScopedPointer d_ptr;

QObjectData

QObjectData中存储了Qt对象的基础数据

class Q_CORE_EXPORT QObjectData {
public:
    virtual ~QObjectData() = 0;
    QObject *q_ptr;   //指向接口类
    QObject *parent; //指向父对象
    QObjectList children; //指向QObject相关的子类列表

    uint isWidget : 1; //占用1bit的内存空间
    uint blockSig : 1;
    uint wasDeleted : 1;
    uint isDeletingChildren : 1;
    uint sendChildEvents : 1;
    uint receiveChildEvents : 1;
    uint isWindow : 1; //for QWindow
    uint deleteLaterCalled : 1;
    uint unused : 24;		//占用24bit的内存空间
    int postedEvents;
    QDynamicMetaObjectData *metaObject;
    QMetaObject *dynamicMetaObject() const;
};

为什么要这样呢
原因是Qt中有一个很重要的设计模式就是句柄实体模式,也就是以QObject为基类的类一般都是句柄类,一般只有一个指针指向一个实体类,在实体类中保存全部的数据
而且一般情况下这个指针还是私有的,方便以后修改句柄类的实现细节
因此,也可以说和句柄类继承关系平行的也有一套实体类派生体系,因此,准确的说,Qt的基类其实有两个,一个是QObject,这是句柄类的唯一基类,另一个是QObjectData,这是实体
QObjectData中的 QObject *q_ptr;
QObject中的QScopedPointer d_ptr;
使得句柄类和实体类可以双向的引用,为什么是这样的命名方式呢?可能q指的是Qt接口类,d指的是Data数据类

QObject parent; //指向父对象
QObjectList children; //指向QObject相关的子类列表
这确实是个大胆的设计,如果系统中产生了1000000个QObject实例(对于大的系统,这个数字很容易达到吧),每个QObject子类平均下来是100(这个数字可能大了),
光这些指针的开销就有1000000
100*4=400M,是够恐怖的,如果我们必须在灵活性和运行开销之间做一个选择的话,无疑Qt选择了前者,对此我也很难评论其中的优劣

再看一下QObject的构造函数

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);
    Q_TRACE(QObject_ctor, this);
}

New QObjectPrivate 给d_ptr
d_ptr不是用来指向数据类QObjectData的吗
其实QObjectPrivate继承自QObjectData
QObjectData只是用来保存数据,QObjectPrivate类封装了线程处理,信号和槽机制等具体的实现,所以后面的比如QWidget的数据类QWidgetPrivate都是直接继承QObjectPrivate

为了方便接口类与数据类的访问,比如QWidget和QWidgetPrivate之间要互相访问但是保存的类型QObjectData和QObjectData都是基类要经过转换, 为了简单起见,Qt声明了两个宏

QObjectData中的 QObject *q_ptr;
QObject中的QScopedPointer d_ptr;

#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;


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

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

而且为了在函数中使用方便,更是直接声明了以下两个宏

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

比如

QWindow *QWidget::windowHandle() const
{
    Q_D(const QWidget);
    return d->windowHandle();
}

void QWidgetPrivate::setModal_sys()
{
    Q_Q(QWidget);
    if (q->windowHandle())
        q->windowHandle()->setModality(q->windowModality());
}
setObjectName
void QObject::setObjectName(const QString &name)
{
    Q_D(QObject);  //获取d指针
    if (!d->extraData) //如果extraData为空 new一个extraData
        d->extraData = new QObjectPrivate::ExtraData;

    if (d->extraData->objectName != name) {  //如果对象名不同 修改对象名 
        d->extraData->objectName = name;
        emit objectNameChanged(d->extraData->objectName, QPrivateSignal()); //发射对象名改变信号
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值