构造函数和初始化列表的性能差距对比测试
1.说明
在C++类和对象中,你可能听到过更加推荐用初始化列表来初始化类内成员。如果类内成员是自定义类型,则只能在初始化列表中调用自定义类型的构造函数。
但初始化列表和在构造函数体内直接赋值有无性能差距呢?今天就用一份相对简单的代码来测试一下
2.测试
2.1 代码说明
首先是一个自定义类型,实现带缺省值的默认构造,拷贝构造和赋值重载这三个函数,并在内部新增打印来区分不同的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct mytest { public: mytest(int a = -1) { _a = a; cout << "mytest() " << a << endl; } mytest(const mytest& st) { _a = st._a; cout << "mytest(copy) " << st._a << endl; } mytest& operator=(const mytest& st) { _a = st._a; cout << "mytest operator= " << st._a << endl; return *this; } private: int _a; };
|
再在另外一个类中使用这个自定义类型
1 2 3 4 5 6 7 8 9
| struct myclass { public: myclass(const struct mytest& st, int b) { cout << "myclass() _b:" << _b << endl; } struct mytest _sa; int _b; };
|
此时构造函数就有两种写法,一种是在初始化列表中初始化这个自定义类型
1 2 3 4 5 6
| myclass(const struct mytest& st, int b) :_sa(st), _b(b) { cout << "myclass() _b:" << _b << endl; }
|
另外一种是在构造函数体内通过赋值重载来初始化这个自定义类型
1 2 3 4 5 6
| myclass(const struct mytest& st, int b) { _sa = st; _b = b; cout << "myclass() _b:" << _b << endl; }
|
需要注意的是,这里的自定义类型传参采用了引用传参,并不会因此产生额外的拷贝!
主函数体如下,为了区分第一个mytest
的构造函数,我在其后新增了一行输出作为分割
1 2 3 4 5 6 7
| int main() { mytest test_a(1); cout << "------" << endl; myclass test(test_a, 3); return 0; }
|
2.2 测试
先来看看使用赋值初始化的方式,可以看到,虽然我们在初始化列表中什么都没有写,但是这里依旧调用了默认的构造函数(因为默认构造函数的缺省值给的是-1
,这里能通过参数判断出来这并不是我们显式调用的构造)
调用了默认构造后,又通过赋值重载来初始化了一遍_sa
,相当于两次初始化
但如果调用初始化列表,则只会有一次拷贝构造,避免了额外的默认构造调用!
在linux下也测试过了,结果和VS2019相同
3.结论
结论就出来了:初始化列表能节省一次默认构造的调用,优化性能!
3.1 实际场景
在上面的场景中,性能差距可能并不会特别大,但是在下面的场景中可能就不一样了
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
| struct mytest { public: mytest(int sz = 1) { _str = new char[sz]; _sz = sz; cout << "mytest() " << sz << endl; } mytest(const mytest& st) { delete _str; _str = new char[st._sz]; _sz = st._sz; cout << "mytest(copy) " << st._sz << endl; } mytest& operator=(const mytest& st) { delete _str; _str = new char[st._sz]; cout << "mytest& operator= " << st._sz << endl; return *this; } private: char* _str; size_t _sz; };
struct myclass { public: myclass(const struct mytest& st, int b) :_sa(st), _b(b) { cout << "myclass() _b:" << _b << endl; } struct mytest _sa; int _b; };
|
在这个场景中,因为mytest
自定义类型的拷贝构造涉及到了深拷贝,此时就需要将已有的空间给销毁了,再new一片新的空间出来,再将数据给拷贝过去。
白白多了一层默认构造中的new空间的+拷贝构造中delete原有空间的消耗!
如果类中需要深拷贝的成员不止一个,那性能差距就更大!
所以在C++中,一律以初始化列表优先!
这里顺带提一嘴初始化列表的小坑,也算是复习;
当你使用初始化列表来初始化类内成员的时候,初始化的顺序是类内成员声明的顺序,而不是初始化列表中的顺序!这点非常重要,如果顺序不对,可能会出现使用未定义(还没有初始化完成)的参数的bug!