C++初始化列表和构造函数体的区别
成员初始化列表
成员初始化列表使用初始化的方式来为数据成员指定初始值。也就是说成员初始化列表是在数据成员定义的同时赋初值。
构造函数体初始化
构造函数的函数体通过赋值的方式来给数据成员指定初始值。也就是说构造函的函数体是采用先定义后赋值的方式来做。
构造函数的两个执行阶段
构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
- 初始化阶段:所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。
- 计算阶段:一般用于执行构造函数体内的赋值操作,下面的代码定义两个结构体,其中Test1有构造函数,拷贝构造函数及赋值运算符,为的是方便查看结果。Test2是个测试类,它以Test1的对象为成员,我们看一下Test2的构造函数是怎么样执行的。
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 | struct Test1 { Test1() // 无参构造函数 { cout << "Construct Test1" << endl ; } Test1(const Test1& t1) // 拷贝构造函数 { cout << "Copy constructor for Test1" << endl ; this->a = t1.a ; } Test1& operator = (const Test1& t1) // 赋值运算符 { cout << "assignment for Test1" << endl ; this->a = t1.a ; return *this; } int a ; }; struct Test2 { Test1 test1 ; Test2(Test1 &t1) { test1 = t1 ; } }; |
调用代码
1 2 3 4 5 6 | Test1 t1 ; Test2 t2(t1) ; 输出: Construct Test1 Construct Test1 assignment for Test1 |
功能上的区别
- 初始化一个引用成员变量
- 初始化一个const变量
- 初始化一个子类对象并且子类对象的父类有一个显示的带参数的构造函数
- 调用一个类类型成员的构造函数并且它拥有一组参数的时候
在这四种情况下是必须要使用成员初始化列表来为这些类型的成员赋初值的。因为这些成员都必须用初始化的方式赋初值。比如引用和const成员变量,这些都是不接受定义之后的赋值的。而对于3的话,是因为子类对象包括父类对象,父类对象既然明确指定了带参的构造函数,那么就必须在构造子类对象的父类部分时显示调用这个构造函数,是不能依赖于合成的默认构造函数的,而这样的话,就必须在成员初始化列表中调用。4也一样,类类型的成员所在类如果有显示定义的构造函数那么也是需要在定义这个成员的同时需要显示调用的。
性能上的区别
1 2 3 4 5 6 7 8 9 10 | class Word { private: String _name; int _cnt; public: Word() { _name = "0"; _cnt = 0; } } |
如果我们创建这样的一个类对象的话,我们可以看出它是用构造函数的函数体来完成数据成员的付初值操作的。对于普通的内置类型还好,但是当有类类型的成员的时候,成员初始化列表和构造函数体两种赋初值的效率就有区别了。上述代码中的构造函数会被编译器扩展成如下形式:
1 2 3 4 5 6 7 | Word::Word() { _name.String::String(); String temp = String(0); _name.String::operator=(temp); temp.String::~String(); _cnt = 0; } |
可以看出,如果使用构造函数体来对类类型付初值的时候,平白无故多了很多步骤和花销。
- 一次默认构造函数的调用
- 一个临时对象temp的创建
- 一次拷贝赋值运算符函数的调用
- 一次析构函数的调用
如果我们现在改成成员列表来初始化呢?
1 2 3 4 | Word::Word() { _name.String::String(0); _cnt = 0; } |
代码果然简洁了许多,也减少了很多花销,前面的方法使用的是拷贝赋值运算符来给数据成员赋初值,而后面的方法是使用类的拷贝构造函数来赋初值。
成员初始化列表的行为
成员初始化列表是可以初始化类的数据成员,那么他是如何操作的呢?是通过一系列的函数调用么,不是的。成员初始化列表是按照数据成员的声明顺序,将初始化操作安排在构造函数所有usercode的前面。成员初始化列表的初始化顺序是按照类成员的声明顺序来的,所以在初始化的时候,尽量不要用次序较后的成员来初始化次序较前的成员,这样就会出问题,这也是成员初始化列表的一个弊端。
转自:https://boblchen.github.io/2016/04/10/%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%E5%92%8C%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E4%BD%93%E7%9A%84%E5%8C%BA%E5%88%AB/