g++ 的 -Wall 选项包含 -Wreorder。该选项的作用如下所述。我不明白为什么有人会关心这个问题(特别是足够关心将其默认打开在 -Wall 中)。
-Wreorder (仅限 C++) 警告当代码中给出成员初始化程序的顺序与执行它们的顺序不匹配时。例如:
struct A { int i; int j; A(): j(0), i(1) { } };
编译器将重新排列 i 和 j 的成员初始化程序以匹配成员的声明顺序,并发出警告。此警告已启用 -Wall。
g++ 的 -Wall 选项包含 -Wreorder。该选项的作用如下所述。我不明白为什么有人会关心这个问题(特别是足够关心将其默认打开在 -Wall 中)。
-Wreorder (仅限 C++) 警告当代码中给出成员初始化程序的顺序与执行它们的顺序不匹配时。例如:
struct A { int i; int j; A(): j(0), i(1) { } };
编译器将重新排列 i 和 j 的成员初始化程序以匹配成员的声明顺序,并发出警告。此警告已启用 -Wall。
考虑:
struct A {
int i;
int j;
A() : j(0), i(j) { }
};
现在,i
被初始化为某个未知值,而不是零。
或者,i
的初始化可能具有某些副作用,其顺序很重要。例如:
A(int n) : j(n++), i(n++) { }
i
初始化为1
)。而这里,i
被初始化为j
,实际上展示了一个问题。 - jazzpii
会被初始化为一个未知的值? - NubcakeA(): j(0), i(j) {}
。有人可能会读到这个代码并认为i最终得到的值是0。但事实上它不是,因为你用j来初始化i,而j本身包含垃圾值,因为它尚未被初始化。A(): i(j), j(0) {}
,希望这看起来更可疑一些。其他答案已经提供了一些很好的例子,证明了警告选项的必要性。我想提供一些历史背景。C++的创造者Bjarne Stroustrup在他的书The C++ programming language(第三版,第259页)中解释:
在执行包含类自己的构造函数之前,成员构造函数就已经被调用。这些构造函数按照它们在类中声明的顺序调用,而不是按照它们在初始化列表中出现的顺序。为了避免混淆,最好按照声明顺序指定初始化程序。成员析构函数按照相反的构造顺序调用。
如果你的初始化器具有副作用,这可能会对你造成影响。请考虑以下示例:
int foo() {
puts("foo");
return 1;
}
int bar() {
puts("bar");
return 2;
}
struct baz {
int x, y;
baz() : y(foo()), x(bar()) {}
};
尽管直觉上人们认为按照初始化列表中的顺序进行初始化,但上述代码将打印出"bar"然后是"foo"。
或者,如果x
和y
是一些具有构造函数的用户定义类型,则该构造函数也可能具有副作用,并得到同样不明显的结果。
这种情况还可能出现在一个成员变量的初始化引用了另一个成员变量的情况下。
j
在i
之前被初始化。如果一个变量用来初始化另一个变量,这就成了问题,例如:struct A {
int i;
int j;
A(): j (0), i (this->j) { }
};
从构造函数的外观来看,这似乎很安全。但实际上,在用于初始化i
的时候,j
尚未被初始化,因此代码不会按预期工作。因此出现了警告。
已经有很多好的回答了,但是还有一件事情没有提到 - 为什么初始化顺序是这样的?
类成员的初始化顺序不是构造函数中初始化器列表中列出的顺序,而是它们在类本身中声明的顺序。原因有点微妙。
C++保证成员变量的销毁顺序与构造顺序相反。由于一个类可以有多个具有不同初始化器列表的构造函数,析构函数需要知道这个顺序,这将导致繁重的簿记问题和不必要的开销。解决方案是强制每个构造函数对成员使用相同的初始化顺序,以便销毁顺序也能固定。
由于无法使用初始化器顺序,唯一的选择就是使用声明顺序。
尽管C++以不同于初始化器列表的顺序初始化变量完全合法,但这可能导致非直观的错误源。该警告让你知道要注意这些错误,或者重新调整初始化器列表以反映实际情况。