当你的自定义类需要利用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, 这个是缺省值. 它表示这个类的实例不能在内存中通过简单的movecpymemmov()来复制和移动, 而是需要调用它的构造/析构函数.

比如说, 我们有个类型Foo, 它存在一个QList里面, 当Qt要增大list的大小时, 会重新分配内存并将原有的实例移动到新的位置. 在缺省情况下, 会对QList中的每个Foo实例调用其拷贝构造函数来重新构造. 如果Foo给打上了PRIMITIVEMOVABLE的标记, 则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)

/* Specialize QTypeInfo for QFlags<T> */
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; }
//Foo1& operator = (const Foo1& other) = default;
//Foo1(Foo1&& val) noexcept : value{val.value}{TRACE() << "Move constru, value=" << value; }
};

//Q_DECLARE_TYPEINFO(Foo1, Q_PRIMITIVE_TYPE);
//Q_DECLARE_TYPEINFO(Foo1, Q_MOVABLE_TYPE);
//Q_DECLARE_TYPEINFO(Foo1, Q_COMPLEX_TYPE);

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);
//list.push_back(std::move(val));
//list.emplace_back(std::move(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函数只是影响调用哪个函数, 并不影响调用不调用函数.