【Qt】元属性内存管理探究及QObject&QMetaObject关系

背景:

学习qt元对象系统,尤其是元属性一块。发现可以用Q_PROPERTY快速定义getter和setter方法,十分方便。且元属性提供了一个非常有用且强大的功能——动态属性。
我们可以通过在运行中添加用户属性,以达到实时增加数据的作用。因此想一探内部的实现机制。一探之前,我们可以假设内部的存储机制:

  1. 简单来说,可以通过一个map等结构实现。
  2. 通过动态调整内存位置来实现。

前者可能性会更大一点。


过程

  1. 找到源码中的部分相关文件
  • QObject(qobject.h、qobject.cpp)
    在qobject.cpp找到QObject::setProperty(const char *name, const QVariant &value)函数。注释上讲到:

如果属性通过Q_PROPERTY进行定义将返回true,否则返回false。如果属性没有通过Q_PROPERTY进行定义,也将说明不会线性化到meta-object中,将会将动态属性加入其中,并返回false。

/*!
  Sets the value of the object's \a name property to \a value.

  If the property is defined in the class using Q_PROPERTY then
  true is returned on success and false otherwise. If the property
  is not defined using Q_PROPERTY, and therefore not listed in the
  meta-object, it is added as a dynamic property and false is returned.

  Information about all available properties is provided through the
  metaObject() and dynamicPropertyNames().

  Dynamic properties can be queried again using property() and can be
  removed by setting the property value to an invalid QVariant.
  Changing the value of a dynamic property causes a QDynamicPropertyChangeEvent
  to be sent to the object.

  \b{Note:} Dynamic properties starting with "_q_" are reserved for internal
  purposes.

  \sa property(), metaObject(), dynamicPropertyNames(), QMetaProperty::write()
*/
bool QObject::setProperty(const char *name, const QVariant &value)
{
    Q_D(QObject);
    const QMetaObject *meta = metaObject();
    if (!name || !meta)
        return false;

    int id = meta->indexOfProperty(name);
    if (id < 0) {
        if (!d->extraData)
            d->extraData = new QObjectPrivate::ExtraData(d);

        const int idx = d->extraData->propertyNames.indexOf(name);

        if (!value.isValid()) {
            if (idx == -1)
                return false;
            d->extraData->propertyNames.removeAt(idx);
            d->extraData->propertyValues.removeAt(idx);
        } else {
            if (idx == -1) {
                d->extraData->propertyNames.append(name);
                d->extraData->propertyValues.append(value);
            } else {
                if (value.userType() == d->extraData->propertyValues.at(idx).userType()
                        && value == d->extraData->propertyValues.at(idx))
                    return false;
                d->extraData->propertyValues[idx] = value;
            }
        }

        QDynamicPropertyChangeEvent ev(name);
        QCoreApplication::sendEvent(this, &ev);

        return false;
    }
    QMetaProperty p = meta->property(id);
#ifndef QT_NO_DEBUG
    if (!p.isWritable())
        qWarning("%s::setProperty: Property \"%s\" invalid,"
                 " read-only or does not exist", metaObject()->className(), name);
#endif
    return p.write(this, value);
}

我们可以在下方的源码中看到,在设置之前会先检查是否存在d->extraData: QObjectPrivate::ExtraData属性,这个属性就是存储参数name和value的数据结构。

  • QObjectPrivate(qobject_p.h)
    我们在qobject_p.h中找到QObjectPrivate类,及其中的ExtraData数据结构。
struct ExtraData
{
    ExtraData(QObjectPrivate *ptr) : parent(ptr) { }

    inline void setObjectNameForwarder(const QString &name)
    {
        parent->q_func()->setObjectName(name);
    }

    inline void nameChangedForwarder(const QString &name)
    {
        Q_EMIT parent->q_func()->objectNameChanged(name, QObject::QPrivateSignal());
    }

    QList<QByteArray> propertyNames;
    QList<QVariant> propertyValues;
    QList<int> runningTimers;
    QList<QPointer<QObject>> eventFilters;
    Q_OBJECT_COMPAT_PROPERTY(QObjectPrivate::ExtraData, QString, objectName,
                                &QObjectPrivate::ExtraData::setObjectNameForwarder,
                                &QObjectPrivate::ExtraData::nameChangedForwarder)
    QObjectPrivate *parent;
};

我们可以看到,这里定义了一系列的QList,用于存储PropertyNames和PropertyValues。


总结

通过上方的查看。可以验证元属性系统,是通过额外的、存储于QObject中的数据结构,进行存储的。这个数据结构,通过线性结构,存储name和value,以实现动态属性系统。基本验证猜想1。
通过实验,也可以知道。用Q_PROPERTY声明的属性,会自动添加到DynamicProperty中,也即上方的线性存储结构中。


扩展

我们知道QObject作为大部分类的基类,提供了QObject::connect()、QObject::tr()等实用函数。但是通过观察源码可以发现,QObject可能调用更底层的类——QMetaObject。
QObject提供一些列文档中可以找到的接口函数,其内部的数据存储到QObjectData结构中(qobject_p.h文件,我们可以找到规律,_p结尾的基本记录的是存储数据的结构)。而QObjectData存储QMetaObject。
找到QMetaObject类,可以打开以下文件:

  1. qmetaobject.h:存储了QMetaObject的成员。包括QMetaMethod、QMetaEnum、QMetaProperty和QMetaClassInfo结构。为QMetaObject使用。
  2. qobjectdefs.h:存储了QMetaObject类。其内部使用上方的结构。
  3. qmetaobject_p.h:存储了QMetaObject相应的、以Private结尾的、用于存储数据的类——QMetaObjectPrivate,通过类大致可以看到是用来存储数据的,符合_p文件的规律。
  4. qmetaobject.cpp:用于存储QMetaObject及其相关类的实现。

为什么说QObject会调用更底层的类QMetaObject实现功能呢?因为我们可以看到QMetaObject内部也实现有QMetaObject::connect()、QMetaObject::tr()等函数。通过查看QObject的tr()函数,发现

#if defined(QT_NO_TRANSLATION) || defined(Q_CLANG_QDOC)
    static QString tr(const char *sourceText, const char * = nullptr, int = -1)
        { return QString::fromUtf8(sourceText); }
#endif // QT_NO_TRANSLATION

其只实现了不需要翻译时候的实现,而没有实现需要进行翻译时候的实现。相反,我们到QMetaObject的第一(qmetaobject.cpp)中,找到了QMetaObject::tr()的实现:

/*!
    \internal
*/
QString QMetaObject::tr(const char *s, const char *c, int n) const
{
    return QCoreApplication::translate(objectClassName(this), s, c, n);
}

可以看到,这里进行了实质性的翻译接口调用。所以有上方的假设。

内部的逻辑还挺复杂,有深入研究过的同学可以分享总结。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值