前面一章我们已经来到了 libs 目录。libs.pro 的SUBDIRS
部分,第一个子项目是 aggregation。因此,我们的代码阅读也就从这里开始入手。
打开 aggregation 目录,按照之前的经验,还是从 aggregation.pro 开始。
include(../../qtcreatorlibrary.pri) DEFINES += AGGREGATION_LIBRARY HEADERS = aggregate.h \ aggregation_global.h SOURCES = aggregate.cpp
这个文件没有那么复杂。但是它的第一行还是把我们带到了另外一个文件,qtcreatorlibrary.pri。qtcreatorlibrary.pri 第一行是这样的:
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
。
第二行,
TARGET = $QTC_LIB_NAME
QTC_LIB_NAME
正是在 aggregation_dependencies.pri 中定义的;可以通过打开这个文件找到具体的值。此时TARGET
值被设置为Aggregation
。因此,我们的类库名字就是 Aggregation。
qtcreatorlibrary.pri 文件剩下的部分没有什么新的东西,所以这里不再展开介绍。
回过头来继续看 aggregation.pro。
接下来一行,使用DEFINES
添加了一个宏定义。后面则是所需要的三个文件:
HEADERS = aggregate.h \ aggregation_global.h SOURCES = aggregate.cpp
我们从 aggregation_global.h 开始看起。
#pragma once #include <qglobal.h> #if defined(AGGREGATION_LIBRARY) # define AGGREGATION_EXPORT Q_DECL_EXPORT #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。
namespace Aggregation { class AGGREGATION_EXPORT Aggregate : public QObject { Q_OBJECT [...] }; [...] } // namespace Aggregation
类库 Aggregation 只有一个类Aggregate
以及很多辅助函数。这些都位于Aggregation
命名空间中。Aggregation
命名空间包含用于“打包”相关组件的类和函数。所谓“打包”,意思是,多个组件组成一个整体,将各自的属性和行为都暴露出来。这个“打包的整体”被称为Aggregate
,也就是Aggregation
命名空间唯一的一个类。
Aggregation::Aggregate
将多个相关组件定义为一个聚合体,从而使外界可以将这些组件视为一个整体。这非常类似一个类实现多个接口,例如:
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
的使用示例。
class MyInterface : public QObject { ........ }; class MyInterfaceEx : public QObject { ........ }; [...] MyInterface *object = new MyInterface;
在这个例子中,MyInterface
和MyInterfaceEx
是两个普通的QObject
子类。object
是MyInterface
的一个实例。Aggregation
命名空间提供了query()
函数,其行为类似于qobject_cast
:
Q_ASSERT(Aggregation::query<MyInterface>(object) == object); Q_ASSERT(Aggregation::query<MyInterfaceEx>(object) == 0);
如果我们希望object
同样实现类MyInterfaceEx
,但是不希望或者根本不能使用多继承,那么,我们就可以使用Aggregate
:
MyInterfaceEx *objectEx = new MyInterfaceEx; Aggregation::Aggregate *aggregate = new Aggregation::Aggregate; aggregate->add(object); aggregate->add(objectEx);
Aggregate
将这两个对象“捆绑”在一起,形成一个聚合体。下面断言都是通过的:
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);
而删除其中任意一个,都会导致整个聚合体的析构:
delete objectEx; // 或 delete object; // 或 delete aggregate;
知道了Aggregate
如何使用,我们就可以看它是如何实现的。
Aggregate::Aggregate(QObject *parent) : QObject(parent) { QWriteLocker locker(&lock()); aggregateMap().insert(this, this); }
由于Aggregate
是QObject
的子类,所以Aggregate
构造函数需要一个QObject
参数。QWriteLocker
加了一个写锁,用于保证在多线程情景下依然能够正常插入。QWriteLocker
简化了QReadWriteLock
锁的写操作,它需要一个QReadWriteLock
锁作为参数,而这个QReadWriteLock
正是lock()
函数返回的。
QReadWriteLock &Aggregate::lock() { static QReadWriteLock lock; return lock; }
aggregateMap()
函数是一个私有静态函数:
[...] 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
。
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
的定义如下:
[...] private: QList<QObject *> m_components; [...]
当对象析构时会发出destroyed()
信号时,会调用聚合体的deleteSelf()
槽函数。该函数是一个私有函数,其实现如下:
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;
调用了析构函数:
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()
函数:
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
中的组件可以被“转换”,其实现代码如下:
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
对象内部的查询。例如,
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
对象,如果是,则从该对象继续尝试查询。其中,两个辅助函数的实现如下:
[...] 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<T>
的query_all()
函数,这与query()
非常类似,这里不再赘述。