当你的自定义类需要利用Qt的容器类存储时, 可以对类做更精细的指定, 从而指导Qt使用更合适的内存管理算法.
Qt里面有一个宏, Q_DECALARE_TYPEINFO
, 它的原型为: Q_DECLARE_TYPEINFO(Type, Flags)
. 其中, Type是要设置的类, 而Flags
可以取下面的值:
Q_PRIMITIVE_TYPE
, 它的意思是这个类的行为类似于基本类型, 可以通过简单地零初始化内存来创建, 也不需要有销毁操作, 并且可以用memcpy()
来创建副本.
参考std::is_trivial
的定义, 它的意思大概也是一样的, 指的是它要么是基本类型, 要么是没有定义函数的POD.
Q_RELOCATABLE_TYPE
, 表示这个类虽然有构造函数和/或析构函数, 但是利用memcpy
在内存中移动.
Q_MOVABLE_TYPE
: 同Q_RELOCATABLE_TYPE
, Qt更建议使用Q_RELOCATABLE_TYPE
. 并且, 这里的MOVABLE
和
Q_COMPLEX_TYPE
, 这个是缺省值. 它表示这个类的实例不能在内存中通过简单的movecpy
和memmov()
来复制和移动, 而是需要调用它的构造/析构函数.
比如说, 我们有个类型Foo
, 它存在一个QList
里面, 当Qt要增大list的大小时, 会重新分配内存并将原有的实例移动到新的位置. 在缺省情况下, 会对QList
中的每个Foo
实例调用其拷贝构造函数来重新构造. 如果Foo
给打上了PRIMITIVE
或MOVABLE
的标记, 则Qt会简单地调用memcpy()
来完成对象的复制. 在性能攸关的使用场景下, 这可能就是很明显的性能收益.
在Qt的帮助中, 是这样说的: Qt将尝试使用std::is_trivial_v<T>
来识别PRIMITIVE
基本类型来检测类型的类,它将需要std::is_trivially_copyable_v<T>
和std::is_trivially_destructible_v<T>
来识别可重定位的类型。这个话有点费解, 看一下实际的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #define Q_DECLARE_TYPEINFO(TYPE, FLAGS) \ template<> \ Q_DECLARE_TYPEINFO_BODY(TYPE, FLAGS)
template<typename T> class QFlags; template<typename T> Q_DECLARE_TYPEINFO_BODY(QFlags<T>, Q_PRIMITIVE_TYPE);
#define Q_DECLARE_TYPEINFO_BODY(TYPE, FLAGS) \ class QTypeInfo<TYPE > \ { \ public: \ enum { \ isComplex = (((FLAGS) & Q_PRIMITIVE_TYPE) == 0) && !std::is_trivial_v<TYPE>, \ isRelocatable = !isComplex || ((FLAGS) & Q_RELOCATABLE_TYPE) || QtPrivate::qIsRelocatable<TYPE>, \ isPointer [[deprecated("Use std::is_pointer instead")]] = std::is_pointer_v< TYPE >, \ isIntegral [[deprecated("Use std::is_integral instead")]] = std::is_integral< TYPE >::value, \ isValueInitializationBitwiseZero = QtPrivate::qIsValueInitializationBitwiseZero<TYPE>, \ }; \ }
namespace QtPrivate { template <typename T> inline constexpr bool qIsRelocatable = std::is_trivially_copyable_v<T> && std::is_trivially_destructible_v<T>;
|
应该是比较清晰的. 下面我们看一下实测的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| struct Foo1 { int value; Foo1(){ TRACE() << "Create "; } Foo1(const Foo1& val) : value{val.value} { TRACE() << "Copy constru, value=" << val.value; } };
void NormalTester::test_case1() { TRACE() << TSHOW(std::is_trivial_v<Foo1>) << TSHOW(std::is_trivially_copy_constructible_v<Foo1>) << TSHOW(std::is_trivially_destructible_v<Foo1>); QList<Foo1> list; for(int i=1; i<10; ++i) { qDebug() << "-----------------------------------------"; TRACE() << "创建对象..." << TSHOW(i) << "添加前队列空间大小: " << list.capacity(); #if 1 Foo1 val; val.value = i; TRACE() << "加入队列..." << TSHOW(val.value); list.push_back(val); #else list.emplace_back(i); #endif TRACE() << "添加后队列空间大小: " << list.capacity(); }
for(int i=0; i<10; ++i) { TRACE() << "检查结果: " << list.at(i).value; } }
|
这个类既不是trivial
类, 也不是trivially copy constructible
类. 我们看一下它的行为:
首先, 把所有的Q_DECLARE_TYPEINFO
都注释掉, 看一下输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] std::is_trivial_v<Foo1>:= false , std::is_trivially_copy_constructible_v<Foo1>:= false , std::is_trivially_destructible_v<Foo1>:= true , QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 1 , 添加前队列空间大小: 0 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 1 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 1 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 1 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 2 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 2 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 2 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 3 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 3 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 3 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 4 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 4 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 4 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 5 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 5 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 5 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 1 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 2 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 3 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 5 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 6 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 6 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 6 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 7 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 7 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 7 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 8 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 8 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 8 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 9 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 9 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 9 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 1 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 2 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 3 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 4 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 5 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 6 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 7 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 8 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 9 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 2097245 PASS : NormalTester::test_case1()
|
这个应该是比较好理解的. 我们打开Q_DECLARE_TYPEINFO(Foo1, Q_COMPLEX_TYPE);
, 也是一样的结果. 我们可以看到随着队列成员添加, 发生了大小的调整, 而在调整的时候, Foo1
的拷贝构造函数被调用了. 我们又故意在最后多打印了一个实例, 发现里面是随机值, 这说明QList
预分配的空间的确并没有做初始化.
现在, 我们打开Q_DECLARE_TYPEINFO(Foo1, Q_MOVABLE_TYPE);
, 运行结果是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] std::is_trivial_v<Foo1>:= false , std::is_trivially_copy_constructible_v<Foo1>:= false , std::is_trivially_destructible_v<Foo1>:= true , QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 1 , 添加前队列空间大小: 0 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 1 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 1 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 1 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 2 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 2 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 2 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 3 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 3 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 3 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 4 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 4 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 4 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 4 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 5 , 添加前队列空间大小: 4 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 5 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 5 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 5 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 6 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 6 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 6 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 7 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 7 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 7 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 8 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 8 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 8 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() ----------------------------------------- QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 创建对象... i:= 9 , 添加前队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Create QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 加入队列... val.value:= 9 , QDEBUG : NormalTester::test_case1() [ Foo1::Foo1 (...)] Copy constru, value= 9 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 添加后队列空间大小: 12 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 1 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 2 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 3 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 4 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 5 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 6 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 7 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 8 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 9 QDEBUG : NormalTester::test_case1() [ NormalTester::test_case1 (...)] 检查结果: 2097245 PASS : NormalTester::test_case1()
|
可以看到当QList
发生内存重新分配时, 并没有调用其拷贝构造函数. 那么, 当存在巨量的成员, 并且函数调用的代价无法被忽视时, 这么做的价值就出来了.
另外, 我们还能看到, Qt是以你在Q_DECLARE_TYPEINFO
里面指定的行为为准的. 你说可以用memcpy
它就真的用memcpy
. 错误自负啦.
另外, Qt说Q_DECALRE_TYPEINFO
的行为和C++的move不move没有关系, 为了加深理解, 我们也可以一起试一下. 结果的确, Foo1
有没有move函数只是影响调用哪个函数, 并不影响调用不调用函数.