夜影如歌

Qt Creator 源码学习 06:aggregation
转自:https://www.devbean.net/2016/10/qt-creator-source-stud...
扫描右侧二维码阅读全文
24
2018/11

Qt Creator 源码学习 06:aggregation

转自:https://www.devbean.net/2016/10/qt-creator-source-study-06/

前面一章我们已经来到了 libs 目录。libs.pro 的SUBDIRS部分,第一个子项目是 aggregation。因此,我们的代码阅读也就从这里开始入手。

打开 aggregation 目录,按照之前的经验,还是从 aggregation.pro 开始。

C/C++8 lines

include(../../qtcreatorlibrary.pri)

DEFINES += AGGREGATION_LIBRARY

HEADERS = aggregate.h \
    aggregation_global.h

SOURCES = aggregate.cpp

这个文件没有那么复杂。但是它的第一行还是把我们带到了另外一个文件,qtcreatorlibrary.pri。qtcreatorlibrary.pri 第一行是这样的:

Plain Text1 lines

include($$replace(_PRO_FILE_PWD_, ([^/]+$), \\1/\\1_dependencies.pri))

 前面已经介绍过$$replace()函数;第一个参数_PRO_FILE_PWD_同样说过。这里值得说明的是后面两个参数。第二个参数([^/]+$)是一个正则表达式。qmake 的正则表达式规则同QRegExp类似,可以参数QRegExp的文档。我们从内向外读。[^/]+$匹配字符串的最后一个 / 字符直到最后结尾的子串;()则是捕获匹配的子串,后面则可以使用\1替换。例如,_PRO_FILE_PWD_的值是E:/Sources/qt-creator/src/libs/aggregation,匹配[^/]+$的部分是aggregation,使用()则将该字符串捕获到\1,最后的\\1/\\1_dependencies.pri部分最终结果是aggregation/aggregation_dependencies.pri。$$replace()函数替换之后的结果是E:/Sources/qt-creator/src/libs/aggregation/aggregation_dependencies.pri。

第二行,

Plain Text1 lines

TARGET = $$QTC_LIB_NAME


QTC_LIB_NAME正是在 aggregation_dependencies.pri 中定义的;可以通过打开这个文件找到具体的值。此时TARGET值被设置为Aggregation。因此,我们的类库名字就是 Aggregation。

qtcreatorlibrary.pri 文件剩下的部分没有什么新的东西,所以这里不再展开介绍。

回过头来继续看 aggregation.pro。

接下来一行,使用DEFINES添加了一个宏定义。后面则是所需要的三个文件:

Plain Text4 lines

HEADERS = aggregate.h \
    aggregation_global.h

SOURCES = aggregate.cpp


我们从 aggregation_global.h 开始看起。

C/C++9 lines

#pragma once

#include <qglobal.h>

#if defined(AGGREGATION_LIBRARY)
#  define AGGREGATION_EXPORT Q_DECL_EXPORT<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>
#else
#  define AGGREGATION_EXPORT Q_DECL_IMPORT
#endif


新版本的 Qt Creator 源代码已经抛弃了宏守卫的形式,转而使用简单的#pragma once预编译语句来保证头文件仅被包含一次。下面的语句定义了AGGREGATION_EXPORT宏,用于动态库的导出导入。由于我们在 aggregation.pro 中定义了宏AGGREGATION_LIBRARY,因此,AGGREGATION_EXPORT将被定义为Q_DECL_EXPORT。这是 Qt 库项目的标准写法,每次使用 Qt Creator 生成代码时总会有类似的形式。

下面来看 aggregate.h。

C/C++9 lines

namespace Aggregation {

class AGGREGATION_EXPORT Aggregate : public QObject
{
    Q_OBJECT
    [...]
};
[...]
} // namespace Aggregation


类库 Aggregation 只有一个类Aggregate以及很多辅助函数。这些都位于Aggregation命名空间中。Aggregation命名空间包含用于“打包”相关组件的类和函数。所谓“打包”,意思是,多个组件组成一个整体,将各自的属性和行为都暴露出来。这个“打包的整体”被称为Aggregate,也就是Aggregation命名空间唯一的一个类。

Aggregation::Aggregate将多个相关组件定义为一个聚合体,从而使外界可以将这些组件视为一个整体。这非常类似一个类实现多个接口,例如:

C/C++3 lines

class Class : public Interface1, public Interface2, public Interface3
{
}


这里,Class类实现了多个接口Interface1、Interface2和Interface1。外部调用者在看待Class类时,可以把它当做接口Interface1、Interface2和Interface1中的任意一个。换句话说,这些接口通过这个类,将自己的属性和行为暴露给外界。

虽然 C++ 支持多继承,但是,多继承通常会带来一些问题,所以一般会避免使用。Aggregate是类似的,只不过它不仅限于接口,从而规避了多继承的问题。Aggregate内部的组件可以是任意QObject的子类。Aggregate可以实现:

Aggregate内部的每个组件都可以相互“转换”
Aggregate内部的每个组件的生命周期都被绑定在一起,例如,其中任意一个被析构,那么,剩余的所有组件也就会被析构
乍看起来,Aggregate类似于类的集合,但是上面两点是Aggregate与集合显著的区别。

下面是一个Aggregate的使用示例。

C/C++4 lines

class MyInterface : public QObject { ........ };
class MyInterfaceEx : public QObject { ........ };
[...]
MyInterface *object = new MyInterface;


在这个例子中,MyInterface和MyInterfaceEx是两个普通的QObject子类。object是MyInterface的一个实例。Aggregation命名空间提供了query()函数,其行为类似于qobject_cast:

C/C++2 lines

Q_ASSERT(Aggregation::query<MyInterface>(object) == object);
Q_ASSERT(Aggregation::query<MyInterfaceEx>(object) == 0);


如果我们希望object同样实现类MyInterfaceEx,但是不希望或者根本不能使用多继承,那么,我们就可以使用Aggregate:

C/C++4 lines

MyInterfaceEx *objectEx = new MyInterfaceEx;
Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
aggregate->add(object);
aggregate->add(objectEx);


Aggregate将这两个对象“捆绑”在一起,形成一个聚合体。下面断言都是通过的:

C/C++4 lines

Q_ASSERT(Aggregation::query<MyInterface>(object) == object);
Q_ASSERT(Aggregation::query<MyInterfaceEx>(object) == objectEx);
Q_ASSERT(Aggregation::query<MyInterface>(objectEx) == object);
Q_ASSERT(Aggregation::query<MyInterfaceEx>(objectEx) == objectEx);


而删除其中任意一个,都会导致整个聚合体的析构:

C/C++3 lines

delete objectEx;
// 或 delete object;
// 或 delete aggregate;


知道了Aggregate如何使用,我们就可以看它是如何实现的。

C/C++6 lines

Aggregate::Aggregate(QObject *parent)
    : QObject(parent)
{
    QWriteLocker locker(&lock());
    aggregateMap().insert(this, this);
}


由于Aggregate是QObject的子类,所以Aggregate构造函数需要一个QObject参数。QWriteLocker加了一个写锁,用于保证在多线程情景下依然能够正常插入。QWriteLocker简化了QReadWriteLock锁的写操作,它需要一个QReadWriteLock锁作为参数,而这个QReadWriteLock正是lock()函数返回的。

C/C++5 lines

QReadWriteLock &Aggregate::lock()
{
    static QReadWriteLock lock;
    return lock;
}


aggregateMap()函数是一个私有静态函数:

C/C++9 lines

[...]
private:
    static QHash<QObject *, Aggregate *> &aggregateMap();
[...]
QHash<QObject *, Aggregate *> &Aggregate::aggregateMap()
{
    static QHash<QObject *, Aggregate *> map;
    return map;
}


它是一个散列,保存每个QObject及其子类与Aggregate的对应关系。因为Aggregate本身就是QObject,因此使用aggregateMap().insert(this, this);表示Aggregate本身隶属于其自己这个聚合体。由于这个函数是静态的,所以所有Aggregate共享一个散列,起到统一注册管理的作用,无需引入第三个管理类来管理这些Aggregate。

C/C++19 lines

void Aggregate::add(QObject *component)
{
    if (!component)
        return;
    {
        QWriteLocker locker(&lock());
        Aggregate *parentAggregation = aggregateMap().value(component);
        if (parentAggregation == this)
            return;
        if (parentAggregation) {
            qWarning() << "Cannot add a component that belongs to a different aggregate" << component;
            return;
        }
        m_components.append(component);
        connect(component, &QObject::destroyed, this, &Aggregate::deleteSelf);
        aggregateMap().insert(component, this);
    }
    emit changed();
}


add()函数用于向Aggregate中添加组件。添加是写操作,所以还是需要写锁。首先,查找需要添加的这个component是否已经在aggregateMap()添加,并且添加到的Aggregate就是this自己。如果是的话,不需要作任何操作,直接返回;如果不是,则会给出一个警告,“这个组件已经被添加到另外的聚合体”。如果该component没有被添加到任何一个聚合体,则添加到自己的m_components属性,并且关联销毁的信号槽,最后还需要到aggregateMap()注册。m_components的定义如下:

Plain Text4 lines

[...]
private:
    QList<QObject *> m_components;
[...]


当对象析构时会发出destroyed()信号时,会调用聚合体的deleteSelf()槽函数。该函数是一个私有函数,其实现如下:

C/C++9 lines

void Aggregate::deleteSelf(QObject *obj)
{
    {
        QWriteLocker locker(&lock());
        aggregateMap().remove(obj);
        m_components.removeAll(obj);
    }
    delete this;
}


移除操作的槽函数在QObject对象发出destroyed()信号,也就是对象析构时会被调用。由于移除操作也是一种写操作,所以这里还是需要写锁。对象被析构,为避免内存泄露,我们需要自己从aggregateMap()和m_components中将其移除。最后,为了实现聚合体内部任意组件被析构,聚合体本身也要被析构这一特性,函数最后做了delete this;操作。delete this;调用了析构函数:

C/C++15 lines

Aggregate::~Aggregate()
{
    QList<QObject *> components;
    {
        QWriteLocker locker(&lock());
        foreach (QObject *component, m_components) {
            disconnect(component, &QObject::destroyed, this, &Aggregate::deleteSelf);
            aggregateMap().remove(component);
        }
        components = m_components;
        m_components.clear();
        aggregateMap().remove(this);
    }
    qDeleteAll(components);
}


在Aggregate的析构函数中,我们需要遍历m_components中的每一个对象,断开其关联的信号槽,然后从aggregateMap()中移除。需要注意的是,这里使用了 Qt 的foreach宏。这个宏自 Qt 5.7 已经不建议使用,具体细节参考这里。QList的清空操作的确会令人疑惑。如果QList中保存的是指针,就像这里,那么,清空QList需要两个步骤:调用clear()函数和qDeleteAll()。前者用于将指针从QList移除,后者会针对每一个指针都调用delete运算符。因为QList并不会持有这些指针指向的内存,所以,每一个元素都需要单独调用delete。这里比较令人不解的是,为什么要将m_components赋值给一个新的QList对象components,然后再调用qDeleteAll()。豆子这里也并不大明白,只是猜测,这是为了将qDeleteAll()的调用移动到写锁之外,毕竟这个操作不需要写同步,而delete可能会比较耗时。如果有不同意见,可以探讨一下。

与add()对应的是remove()函数:

C/C++12 lines

void Aggregate::remove(QObject *component)
{
    if (!component)
        return;
    {
        QWriteLocker locker(&lock());
        aggregateMap().remove(component);
        m_components.removeAll(component);
        disconnect(component, &QObject::destroyed, this, &Aggregate::deleteSelf);
    }
    emit changed();
}


remove()函数用于移除聚合体中的组件。其实现与add()类似。在添加了写锁之后,首先从aggregateMap()中移除对应的组件,然后将其从自己的m_components中全部删除,最后再将信号槽断开连接。最后一个断开的操作是必要的,因为我们只是从聚合体中移除对象,当对象析构时,我们不希望其所在聚合体也会被析构(这是我们在add()操作中关联的)。

现在,我们已经分析了最重要的两个操作,add()和remove()。当组件被添加到聚合体之后,剩下的就是转换和查询。

前面我们提到,被添加到Aggregate中的组件可以被“转换”,其实现代码如下:

C/C++19 lines

template <typename T> T *component() {
    QReadLocker locker(&lock());
    foreach (QObject *component, m_components) {
        if (T *result = qobject_cast<T *>(component))
            return result;
    }
    return (T *)0;
}

template <typename T> QList<T *> components() {
    QReadLocker locker(&lock());
    QList<T *> results;
    foreach (QObject *component, m_components) {
        if (T *result = qobject_cast<T *>(component)) {
            results << result;
        }
    }
    return results;
}


这两个函数类似,只不过一个用于转换成一个对象,另外一个用于转换成一组对象。函数使用读锁来保证线程安全,通过遍历m_components中每一个对象,使用qobject_cast来判断是否所需要的类型,然后将符合的对象返回。

Aggregation命名空间提供了全局的查询函数,用于替代qobject_cast。这些函数比qobject_cast更实用,因为它们支持Aggregate对象内部的查询。例如,

C/C++12 lines

template <typename T> T *query(QObject *obj)
{
    if (!obj)
        return (T *)0;
    T *result = qobject_cast<T *>(obj);
    if (!result) {
        QReadLocker locker(&Aggregate::lock());
        Aggregate *parentAggregation = Aggregate::parentAggregate(obj);
        result = (parentAggregation ? query<T>(parentAggregation) : 0);
    }
    return result;
}


query()用于将obj对象转换成所需要的类型。首先,它会尝试使用qobject_cast进行转换,如果转换成功则直接返回,否则会查询其是否存在于某个Aggregate对象,如果是,则从该对象继续尝试查询。其中,两个辅助函数的实现如下:

C/C++13 lines

[...]
Aggregate *Aggregate::parentAggregate(QObject *obj)
{
    QReadLocker locker(&lock());
    return aggregateMap().value(obj);
}
[...]
template <typename T> T *query(Aggregate *obj)
{
    if (!obj)
        return (T *)0;
    return obj->template component<T>();
}


Aggregation同样提供了返回QList的query_all()函数,这与query()非常类似,这里不再赘述。

最后修改:2018 年 11 月 24 日 10 : 26 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论