就地初始化&初始化列表
就地初始化:member initializer list
初始化列表:member initializer list,或:member initialization list
参考:https://www.cnblogs.com/lidabo/p/3628987.html
C++构造函数初始化按下列顺序被调用:
- 首先,任何虚拟基类的构造函数按照它们被继承的顺序构造;
- 其次,任何非虚拟基类的构造函数按照它们被继承的顺序构造;
- 最后,任何成员对象的构造函数按照它们声明的顺序调用;
由于类成员初始化总在构造函数执行之前,编译器总是确保所有成员对象在构造函数体执行之前初始化,
参考:https://www.cnblogs.com/simplepaul/p/7635648.html
在C++11中,直接对成员变量赋值(就地初始化),和初始化列表,哪个先执行,哪个后执行?
例如:
class MyClass { private: int a = 10; public: MyClass() :a(20) {} ~MyClass(){} int getA() { return a; } };
构造一个MyClass后,a的值是多少?
条款4:C++“成员初始化次序”:base class更早于其derived class被初始化,而class的成员变量总是以其声明次序被初始化,即使成员变量在成员初始化列表中的次序不同。
这里只是说了成员初始化的顺序
但是:在构造MyClass中,已经初始化了a,然后再在初始化列表中调用拷贝构造函数吗?待查
写了如下测试代码:
class B { public: explicit B(int a) { this->b = a; cout << "construct:" << a << endl; }
B(const B &val)
{
cout << "copy:" << val.b << endl;
this->b = val.b;
}
~B() { cout << "Destruct:" << this->b << endl; } private: int b; }; class MyClass { private: int a = 10; B b = B(3); public: MyClass() :a(20), b(B(4)) {} ~MyClass(){} int getA() { return a; } }; int main() { MyClass aa; cout << "val a:" << aa.getA() << endl; int val = aa.getA(); }
输出的打印,类B只构造了一次。。。即成员初始化列表中的4那次。
为啥嘞???
相关文章:https://cloud.tencent.com/developer/article/1394301
这篇文章说的是,就地初始化在先,然后成员列表初始化:
就地初始化与初始化列表的先后顺序
C++11标准支持了就地初始化非静态数据成员的同时,初始化列表的方式也被保留下来,也就是说既可以使用就地初始化,也可以使用初始化列表来完成数据成员的初始化工作。当二者同时使用时,并不冲突,初始化列表发生在就地初始化之后,即最终的初始化结果以初始化列表为准。
Note:
上面链接的文章说的不准确,参考:https://en.cppreference.com/w/cpp/language/constructor,这个文章中说,如果成员变量就地初始化,成员变量又出现在初始化列表中,则就地初始化被忽略,以初始化列表中的为准。
If a non-static data member has a default member initializer and also appears in a member initializer list, then the member initializer is used and the default member initializer is ignored: struct S {
int n = 42; // default member initializer
S() : n(7) {} // will set n to 7, not 42
};
|
该文章中对构造函数解释如下:
构造函数没有函数名且不能直接被调用。构造函数在初始化发生时被调用,且根据初始化的规则选择特定的构造函数。没有explicit说明符的是可以隐式转换的构造函数。带有constexpr说明符的构造函数成为一个字面类型(LiteralType)。没够任何参数的构造函数为默认构造函数 default constructor。以另一个同类型对象作为的参数的构造函数有拷贝构造函数 copy constructor,和移动构造函数 move constructor。
在构造函数的函数体执行之前(即{}内的语句),所有的父类、虚类、non-static成员变量都已初始化完成。成员初始化列表(member initializer list)可用于父类、虚类、非静态成员变量的初始化(非默认的初始化,non-default initialization)。对于不能调用默认构造函数构造的基类、non-static成员变量,必须在初始化列表中进行初始化,例如:引用类型、const类型的成员变量。
什么情况必须使用初始化列表
什么时候必须使用初始化列表?《深入理解C++对象模型》中描述如下:
成员初始化顺序
如果在构造函数中,通过赋值的形式对成员变量进行初始化,如下所示:
这种情况,构造函数可能的内部扩张结果为:
也就是说,在构造函数中,编译器先调用默认构造函数初始化了 _name ,_cnt,然后再通过赋值操作符(operator =)对其进行赋值操作,效率较差。
而采用初始化列表形式,如:
它会被编译器扩张为如下代码:
即直接调用拷贝构造函数(copy constructor)对成员变量进行构造,效率较高。(注意:不论在初始化列表中写的初始化顺序是什么,成员变量的初始化顺序按照在类的声明中的顺序进行,条款4)
在本例中,_name 先于 _cnt声明,则 _name先于_cnt初始化,“初始化次序” 和 “initialization list中的项目排列次序”是不同的。
构造函数调用构造函数
在C++11之后,可以在构造函数中调用构造函数:如果一个类中有多个构造函数,为避免代码的重复,可以在一个构造函数中的初始化列表中调用另一个构造函数,但是注意构造函数调用不能出现循环情况。例如:
class Foo {
public:
Foo(char x, int y) {}
Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
};
下面是cppreference给出的示例代码,包含大部分情况:
#include <fstream> #include <string> #include <mutex> struct Base { int n; }; struct Class : public Base { unsigned char x; unsigned char y; std::mutex m; std::lock_guard<std::mutex> lg; std::fstream f; std::string s; Class ( int x ) : Base { 123 }, // initialize base class x ( x ), // x (member) is initialized with x (parameter) y { 0 }, // y initialized to 0 f{"test.cc", std::ios::app}, // this takes place after m and lg are initialized s(__func__), //__func__ is available because init-list is a part of constructor lg ( m ), // lg uses m, which is already initialized m{} // m is initialized before lg even though it appears last here {} // empty compound statement Class ( double a ) : y ( a+1 ), x ( y ), // x will be initialized before y, its value here is indeterminate lg ( m ) {} // base class initializer does not appear in the list, it is // default-initialized (not the same as if Base() were used, which is value-init) Class() try // function-try block begins before the function body, which includes init list : Class( 0.0 ) //delegate constructor { // ... } catch (...) { // exception occurred on initialization } }; int main() { Class c; Class c1(1); Class c2(0.1); }