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

eqfvzcg8  于 2023-03-20  发布在  其他
关注(0)|答案(3)|浏览(127)

初始化列表总是在构造函数代码之前处理吗?
换句话说,下面的代码是否总是打印<unknown>,并且构造的类将“已知”为source_的值(如果全局变量somethingtrue)?

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

anauzrmj1#

是,根据C++11: 12.6.2 /10C++14中的相同部分,C++17中的15.6.2 /13):
在非委托构造函数中,初始化按以下顺序进行(我的粗体):

  • 首先,并且仅对于最大派生类的构造器(1.8),虚拟基类以它们在基类的有向非循环图的深度优先的从左到右遍历中出现的顺序被初始化,其中“从左到右”是基类在派生类基类说明符列表中出现的顺序。
  • 然后,直接基类按照它们出现在base-specifier-list中的声明顺序初始化(不管mem-initializer的顺序如何)。
  • 然后,非静态数据成员按照它们在类定义中声明的顺序初始化(同样与mem-initializer的顺序无关)。
    ****最后,***执行构造函数体的compound-statement。

使用初始化列表的主要原因是为了帮助编译器进行优化。非基本类型的初始化列表(即类对象而不是intfloat等)通常可以就地构造。
如果创建对象,然后在构造函数中对其赋值,这通常会导致创建和销毁临时对象,效率很低。
初始化列表可以避免这种情况(当然,如果编译器能够胜任的话,但大多数都应该胜任)。
下面的完整程序将输出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;
}
knpiaxh1

knpiaxh12#

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

mbskvtky

mbskvtky3#

正如已经回答的,初始化列表在进入构造函数块之前就已经完全执行了,所以在构造函数体中使用(初始化)成员是完全安全的。
你在可接受的答案中做了一个注解,关于必须 * 引用构造函数参数,而不是构造函数块中的成员vars*。
有可能你误解了这样一个事实:你应该引用参数,而不是引用初始化列表中的成员属性。举个例子,给定一个类X,它有两个int类型的成员(a_和b_),下面的构造函数可能是定义不良的:

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_。这将产生一个bug,因为你的代码相信(并且可能依赖于)b_是a_值的两倍,而事实上b_包含垃圾。最简单的解决方案是不引用成员:

X::X( int a ) : a_( a ), b( a*2 ) {} // correct regardless of how X is declared

为了避免此陷阱,建议您不要将成员属性用作其他成员初始化的一部分。

相关问题