前面一章我们已经来到了 libs 目录。libs.pro 的
SUBDIRS
部分,第一个子项目是 aggregation。因此,我们的代码阅读也就从这里开始入手。
打开 aggregation 目录,按照之前的经验,还是从 aggregation.pro 开始。
include(../../qtcreatorlibrary.pri)DEFINES += AGGREGATION_LIBRARYHEADERS = aggregate.h \ aggregation_global.hSOURCES = 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.hSOURCES = aggregate.cpp
我们从 aggregation_global.h 开始看起。
#pragma once#include #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(object) == object);Q_ASSERT(Aggregation::query(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 &aggregateMap();[...]QHash &Aggregate::aggregateMap(){ static QHashmap; 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 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 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(component)) return result; } return (T *)0;}template <typename T> QList components() { QReadLocker locker(&lock()); QList results; foreach (QObject *component, m_components) { if (T *result = qobject_cast(component)) { results << result; } } return results;}
这两个函数类似,只不过一个用于转换成一个对象,另外一个用于转换成一组对象。函数使用读锁来保证线程安全,通过遍历
m_components
中每一个对象,使用
qobject_cast
来判断是否所需要的类型,然后将符合的对象返回。
Aggregation
命名空间提供了全局的查询函数,用于替代
qobject_cast
。这些函数比
qobject_cast
更实用,因为它们支持
Aggregate
对象内部的查询。例如,
template T *query(QObject *obj){ if (!obj) return (T *)0; T *result = qobject_cast(obj); if (!result) { QReadLocker locker(&Aggregate::lock()); Aggregate *parentAggregation = Aggregate::parentAggregate(obj); result = (parentAggregation ? query(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();}
Aggregation
同样提供了返回
QList
的
query_all()
函数,这与
query()
非常类似,这里不再赘述。