18

初始化列表是否总是在构造函数代码之前处理?

换句话说,以下代码是否总是 print <unknown>,并且构造的类将具有“已知”作为值source_(如果全局变量somethingtrue)?

class Foo {
  std::string source_;
public:
  Foo() : source_("<unknown>") {
    std::cout << source_ << std::endl;
    if(something){
      source_ = "known";
    }
  }
};
4

3 回答 3

24

是的,它会按照C++11: 12.6.2 /10(中的相同部分C++1415.6.2 /13C++17):


在非委托构造函数中,初始化按以下顺序进行(我的粗体):

  • 首先,并且仅对于最派生类(1.8)的构造函数,虚拟基类按照它们出现在基类的有向无环图的深度优先从左到右遍历的顺序进行初始化,其中“左- to-right”是派生类base-specifier-list中基类的出现顺序。

  • 然后,直接基类按照它们出现在 base-specifier-list 中的声明顺序进行初始化(无论 mem-initializers 的顺序如何)。

  • 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样不管 mem-initializers 的顺序)。

  • 最后,执行构造函数主体的复合语句。


使用 init-lists 的主要原因是帮助编译器进行优化。非基本类型(即类对象而不是 , 等)的初始化列表int通常float可以就地构建。

如果创建对象然后在构造函数中赋值给它,这通常会导致临时对象的创建和销毁,这是低效的。

初始化列表可以避免这种情况(当然,如果编译器可以做到,但大多数都应该这样做)。

以下完整程序将输出 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;
}
于 2009-04-07T02:30:46.357 回答
8

是的,C++ 在调用构造函数代码之前构造所有成员。

于 2009-04-07T02:31:16.450 回答
7

正如已经回答的那样,初始化列表在进入构造函数块之前完全执行。所以在构造函数体中使用(初始化的)成员是完全安全的。

您在接受的答案中发表了关于必须引用构造函数参数而不是构造函数块内的成员变量的评论。你没有。

您可能误认为您应该引用参数而不是初始化列表中的成员属性。例如,给定一个具有两个 int 类型成员(a_ 和 b_)的类 X,以下构造函数可能定义不正确:

 X::X( int a ) : a_( a ), b( a_*2 ) {}

这里可能的问题是初始化列表中元素的构造取决于类中声明的顺序,而不是您键入初始化列表的顺序。如果类被定义为:

class X
{
public:
   X( int a );
private:
   int b_;
   int a_; 
};

然后,无论您如何键入初始化列表,事实是 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

避免这个陷阱是建议您不要将成员属性用作其他成员初始化的一部分的原因。

于 2009-04-07T06:05:04.627 回答