Fra*_*ank 15 c++ constructor initialization
是否始终在构造函数代码之前处理初始化列表?
换句话说,下面的代码是否总是打印出来<unknown>,并且构造的类将具有"已知"作为值source_(如果全局变量something是true)?
class Foo {
std::string source_;
public:
Foo() : source_("<unknown>") {
std::cout << source_ << std::endl;
if(something){
source_ = "known";
}
}
};
Run Code Online (Sandbox Code Playgroud)
pax*_*blo 20
是的,它会,按照C++11: 12.6.2 /10(同一节中C++14,15.6.2 /13在C++17):
在非委托构造函数中,初始化按以下顺序进行(我的粗体):
首先,仅对于派生程度最高的类(1.8)的构造函数,虚拟基类按照它们出现在基类的有向无环图的深度优先从左到右遍历的顺序进行初始化,其中"左 - to-right"是派生类base-specifier-list中基类出现的顺序.
然后,直接基类按声明顺序初始化,因为它们出现在base-specifier-list中(无论mem-initializers的顺序如何).
然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样不管mem-initializers的顺序如何).
最后,执行构造函数体的复合语句.
使用init-lists的主要原因是帮助编译器进行优化.INIT-列出了非基本类型(即,类对象,而不是int,float等)通常可以就地构建.
如果您创建对象然后在构造函数中分配它,这通常会导致临时对象的创建和销毁,这是低效的.
Init-lists可以避免这种情况(如果编译器可以使用它,当然但大多数应该是这样).
以下完整程序将输出7,但这是针对特定编译器(CygWin g ++),因此它不保证该行为不仅仅是原始问题中的样本.
但是,根据上面第一段中的引用,该标准确实保证了它.
#include <iostream>
class Foo {
int x;
public:
Foo(): x(7) {
std::cout << x << std::endl;
}
};
int main (void) {
Foo foo;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如前所述,初始化列表在进入构造函数块之前完全执行.因此,在构造函数体中使用(初始化的)成员是完全安全的.
您已经在接受的答案中对必须引用构造函数参数进行了注释,而不是构造函数块中的成员变量.你没有.
这可能是你弄错了事实,你应该参考的参数,而不是成员属性内的初始化列表.例如,给定一个类X具有int类型的两个成员(a_和b_),以下构造函数可能是不明确的:
X::X( int a ) : a_( a ), b( a_*2 ) {}
Run Code Online (Sandbox Code Playgroud)
这里可能存在的问题是初始化列表中元素的构造取决于类中声明的顺序,而不是您键入初始化列表的顺序.如果该类被定义为:
class X
{
public:
X( int a );
private:
int b_;
int a_;
};
Run Code Online (Sandbox Code Playgroud)
然后,无论您如何键入初始化列表,事实是b_(a_*2)将在 a_初始化之前执行,因为成员的声明首先是b_,后来是a_.这会产生一个错误,因为你的代码认为(并且可能取决于)b_是a_的两倍,实际上b_包含垃圾.最简单的解决方案不是指成员:
X::X( int a ) : a_( a ), b( a*2 ) {} // correct regardless of how X is declared
Run Code Online (Sandbox Code Playgroud)
避免这个陷阱是建议您不要将成员属性用作其他成员初始化的一部分的原因.