派生类初始化列表中调用基类构造函数的顺序

11
struct B { 
  int b1, b2;  
  B(int, int);
};

struct D : B {
  int d1, d2;
// which is technically better ?
  D (int i, int j, int k, int l) : B(i,j), d1(k), d2(l) {} // 1st Base
// or
  D (int i, int j, int k, int l) : d1(k), d2(l), B(i,j) {} // last Base
};

以上仅为伪代码。实际上,我想知道调用基本构造函数的顺序是否重要?

在任何情况下,是否存在任何不良行为(特别是边角情况)引起的问题?我的问题更多关注技术方面,而不是编码风格。

3个回答

21
您在问题中提到的顺序并不是“调用基类构造函数的顺序”。实际上,您无法调用构造函数。构造函数不能被用户调用,只能由编译器调用。
您可以做的是指定初始值。在这种情况下(构造函数初始化列表),您正在为某个较大对象的子对象指定初始化程序。您指定这些初始化程序的顺序并不重要:编译器将按照语言规范定义的非常特定的顺序调用构造函数,而不考虑您指定初始化程序的顺序。基类构造函数始终首先调用(按照基类在类定义中列出的顺序),然后调用成员子对象的构造函数(同样按照这些成员在类定义中列出的顺序)。
当涉及到虚基类时,此规则存在一些奇特之处,但我决定不在此处包括它们。
至于错误行为...当然,在这里存在“错误行为”的可能性。如果您假设初始化顺序取决于构造函数初始化程序列表中使用的顺序,那么您最有可能最终会遇到令人不愉快的惊喜,即发现编译器完全忽略了该顺序并使用其自己的顺序(声明顺序)。例如,这段代码的作者:
struct S {
  int b, a;
  S() : a(5), b(a) {}
};

在代码中,你可能期望变量a先被初始化,然后变量ba中获取初始值5。但实际上这不会发生,因为b会在a之前被初始化。


3
值得一提的是,虚拟基类在初始化基类的顺序中优先考虑。 - Alok Save
2
@Als:……而且虚基类初始化程序只有在构造最派生对象时才会被简单地忽略。我不想用这些问题来压垮我的答案。 - AnT stands with Russia
3
构造函数不可由用户调用。只有编译器可以调用构造函数。难道不是使用放置 new 手动调用构造函数吗?或者这被认为是“运行”构造函数吗? - Nikos Athanasiou
值得指出的是,由于基础构造函数首先被调用,在初始化列表中将其列在第一位是最合理的。实际上,编译器可以对此发出警告(例如,在gcc中使用“-Wreorder”)。 - Zitrax

6

这里的顺序是明确定义的,它不取决于您在初始化时如何指定。
基类构造函数B将首先被调用,然后按照声明的顺序调用成员变量(d1d2)。

为了解释@Andrey T答案中的注释。

class MyClass1: public MyClass2, public virtual MyClass3
{


};

调用基类构造函数的顺序在标准中有明确定义,将会是:
MyClass3  
MyClass2
MyClass1

虚拟基类MyClass3比基类MyClass2更优先使用。

你能解释一下你在答案中提到的“虚拟”基类吗? - iammilind
为什么虚基类比myclass2优先使用? @aloksave - Krishna Oza
@darth_coder virtual 基类优先考虑,因为它们必须在任何依赖于它们的非 virtual 基类之前构造。考虑以下情况:struct A{}; struct B : virtual A {}; struct C: virtual A {}; struct D : A, B {};。在这种情况下,D 必须在构造 BC 之前构造 A,因为 BC 都要求在它们之前构造 A。与其允许 BC 构造 A(在更复杂的情况下可能会变得复杂,例如如果还有 struct E : C, B {};),需要额外的编译器工作。 - Justin Time - Reinstate Monica
开销可能会增加,并且可能会引入未定义的行为(如果简单编译器决定B总是构造A,那么E将具有UB,因为它的C在其B可以构造其A之前被构造)或打破期望(如果该编译器默默地、恶意地在C之前构造B,忽略了E:C,B,那么这就违反了最小惊讶原则)。标准只是规定最派生类必须在构造任何非虚拟基类之前构造其继承层次结构中的任何虚拟基类,从而保证了。 - Justin Time - Reinstate Monica
当构造BC时,无论它们的构造顺序如何,A始终有效,从而防止任何未定义行为。为了缩短这堵墙的文本,virtual基类被优先考虑,以确保在构造非virtual基类时,它们始终有效,从而防止UB和/或使编译器违反标准以防止UB。 - Justin Time - Reinstate Monica

3

初始化列表中物品出现的顺序不重要。在您的情况下,基础对象将始终首先初始化,然后是d1和d2,按此顺序进行。初始化按派生顺序和按类定义中成员出现的顺序进行。

话虽如此,通常认为按初始化顺序编写初始化列表是良好的风格,并且如果您不这样做,某些编译器会发出警告。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接