在Effective C ++中,据说初始化列表中的数据元素需要按其声明的顺序列出。进一步说,其原因是数据元素的析构函数以其构造函数的相反顺序调用。
但我只是看不出这是怎么回事......
答案 0 :(得分:25)
请考虑以下事项:
class Class {
Class( int var ) : var1( var ), var2(var1 ) {} // allright
//Class( int var ) : var2( var ), var1(var2 ) {} // var1 will be left uninitialized
int var1;
int var2;
};
第二个(已注释掉的)构造函数看起来很好,但实际上只会初始化var2
- var1
将首先初始化,并且将使用尚未编译的var2
进行初始化在那时初始化。
如果按照与成员变量相同的顺序列出初始化程序,则在类声明中列出此类错误的风险会降低很多。
答案 1 :(得分:10)
当成员也是某种以某种方式相互依赖的类的对象时,构造和破坏的顺序可能很重要。
考虑一个简单的例子:
class MyString {
public:
size_t s_length;
std::string s;
MyString(const char *str) : s(str), s_length(s.length()) {}
};
此示例中的意图是成员s_length
保存存储字符串的长度。但是,这不起作用,因为s_length
将在 s
之前初始化。因此,在执行s.length
的构造函数之前调用s
!
答案 2 :(得分:7)
例如,如果您有这样的类:
class X {
int a,b;
X(int i) : b(i),a(b) { } // Constructor
};
类X的构造函数看起来首先初始化“b”,但它实际上是按声明的顺序初始化。这意味着它将首先初始化“a”。然而,“a”初始化为尚未初始化的“b”值,因此“a”将获得垃圾值。
答案 3 :(得分:3)
破坏与构造相反,因此元素以相反的顺序被破坏。
我们说我们有2名成员,a
和b
。 b
取决于a
,但a
不依赖于b
。
当我们构建时,我们首先构造a
,现在它存在,我们可以构造b
。当我们破坏时,如果我们首先破坏a
这将是一个问题,因为b
取决于它。但我们首先破坏b
并确保完整性。
这是典型的。例如,在群论中,fg
的倒数是~g~f
(其中~f
是f
的倒数)
当你穿着时,你首先穿上袜子,然后穿上鞋子。当你脱衣服时,先脱掉鞋子,然后取下袜子。
答案 4 :(得分:2)
如果您的成员的一个构造函数抛出异常,这也可能是一个问题。然后,所有已经正确构造的成员必须以某些顺序进行破坏,因为没有类似于析构函数的初始化列表。此顺序与类声明中成员的外观相反。一个例子:
#include <iostream>
struct A1 {
A1(int) { std::cout << "A1::A1(int)" << std::endl; }
~A1() { std::cout << "A1::~A1()" << std::endl; }
};
struct A2 {
A2(int) { std::cout << "A2::A2(int)" << std::endl; }
~A2() { std::cout << "A2::~A2()" << std::endl; }
};
struct B {
B(int) { std::cout << "B::B(int)" << std::endl; throw 1; }
~B() { std::cout << "B::~B()" << std::endl; }
};
struct C {
C() : a1(1), a2(2), b(3) { std::cout << "C::C()" << std::endl; } // throw 1; }
~C() { std::cout << "C::~C()" << std::endl; }
A1 a1;
A2 a2;
B b;
};
int main() {
try {
C c;
} catch (int i) {
std::cout << "Exception!\n";
}
}
输出将是这样的:
A1::A1(int)
A2::A2(int)
B::B(int)
A2::~A2()
A1::~A1()
Exception!
答案 5 :(得分:1)
进一步说是推理 对于这是数据的析构函数 元素被反向调用 他们的建设者的顺序。
请参阅Steve Jessop在Class component order of initialisation
的评论