Qt Creator 源码学习 06:aggregation

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

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

这个文件没有那么复杂。但是它的第一行还是把我们带到了另外一个文件,qtcreatorlibrary.pri。qtcreatorlibrary.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

第二行,

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

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

回过头来继续看 aggregation.pro。

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

我们从 aggregation_global.h 开始看起。

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

下面来看 aggregate.h。

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

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

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

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

  • Aggregate内部的每个组件都可以相互“转换”
  • Aggregate内部的每个组件的生命周期都被绑定在一起,例如,其中任意一个被析构,那么,剩余的所有组件也就会被析构

乍看起来,Aggregate类似于类的集合,但是上面两点是Aggregate与集合显著的区别。

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

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

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

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

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

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

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

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

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

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

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

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

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()函数:

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

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

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

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

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

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

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

上一篇
下一篇

Leave a Reply