如果需要使用Qt容器,那么使用Q_DECLARE_TYPEINFO
让Qt了解容器内元素的类型特征是一个不错的做法。因为Qt可以通过识别Q_DECLARE_TYPEINFO
给定的类型特征,在容器中采用不同的算法和内存模型以达到计算速度和内存使用上的优化。
Q_DECLARE_TYPEINFO(Type, Flags)
的使用非常简单,在定义了数据结构之后,通过指定类型名和枚举标识来指定这个类型特征,例如:
struct Point2D |
Q_PRIMITIVE_TYPE
指定Type是没有构造函数或析构函数的POD(plain old data) 类型,并且memcpy()
可以这种类型创建有效的独立副本的对象。Q_MOVABLE_TYPE
指定Type具有构造函数或析构函数,但可以使用memcpy()
在内存中移动。注意:尽管有叫做move,但是这个和移动构造函数或移动语义无关。Q_COMPLEX_TYPE
(默认值)指定Type具有构造函数和析构函数,并且可能不会在内存中移动。
再来看看Q_DECLARE_TYPEINFO
的具体实现:
template <typename T> |
可以看出Q_DECLARE_TYPEINFO
是一个典型的模板特化和模板enum hack结合的例子,代码使用宏Q_DECLARE_TYPEINFO_BODY
定义了一个QTypeInfo
的特化版本class QTypeInfo<TYPE >
,并且使用定义给定的标志,计算出了一系列枚举值,例如isComplex
、isStatic
等。
Qt预定义了自己类型的QTypeInfo
以便让它们在容器中获得更高的处理效率,例如:
// 基础类型 |
最后让我们来看看Qt容器如何利用QTypeInfo
来优化容器算法的。以我们之前介绍过的QList
为例,QList
会因为类型大小的不同采用不同的内存布局和构造方法:
template <typename T> |
根据以上代码可以看出,QList
根据QTypeInfo
中isLarge
、isStatic
和isComplex
的不同采用不同的构造析构和拷贝方法。以构造为例,当表达式QTypeInfo<T>::isLarge || QTypeInfo<T>::isStatic
计算结果为true
时,QList
从堆里分配新内存并且构造对象存储在node
中。当QTypeInfo<T>::isComplex
的计算结果为true
时,QList
采用Placement new的方式直接使用node
内存构造对象。除此之外则简单粗暴的拷贝内存到node
内存上。析构和拷贝也有相似处理,阅读代码很容易理解其中的含义。
我们应该怎么做?Qt默认情况下会认为类型特征isStatic
为true
,这会导致一些不必要的性能下降,例如QList
会无视类型大小,采用从堆重新分配内存构造对象。所以我们应该做的是充分理解我们的对象类型是否可以安全的移动,如果可以移动请使用Q_DECLARE_TYPEINFO(TYPE, Q_MOVABLE_TYPE);
告知Qt,这样当对象长度小于指针长度的时候,Qt可以避免访问堆来分配内存,并且直接利用已有内存,对于频繁发生的小尺寸对象的操作这种优化是非常巨大的。
Run on (8 X 3600 MHz CPU s) |
详细可以阅读前面的文章《QList
的工作原理和运行效率浅析》。