在Effective C++中,据说初始化列表中的数据元素需要按照它们的声明顺序列出。进一步说,这样做的原因是数据元素的析构函数以其构造函数的相反顺序被调用。
但我只是不明白这怎么可能是一个问题......
在Effective C++中,据说初始化列表中的数据元素需要按照它们的声明顺序列出。进一步说,这样做的原因是数据元素的析构函数以其构造函数的相反顺序被调用。
但我只是不明白这怎么可能是一个问题......
考虑以下几点:
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
当时尚未初始化的那个。
如果您以与在类声明中列出成员变量相同的顺序列出初始化程序,则此类错误的风险会大大降低。
当成员也是某个类的对象,它们以某种方式相互依赖时,构造和销毁的顺序可能很重要。
考虑一个简单的例子:
class MyString {
public:
size_t s_length;
std::string s;
MyString(const char *str) : s(str), s_length(s.length()) {}
};
此示例中的意图是 members_length
保存存储字符串的长度。但是,这不起作用,因为s_length
将在 s
. 所以你s.length
在构造函数s
执行之前调用!
例如,如果您有这样的课程:
class X {
int a,b;
X(int i) : b(i),a(b) { } // Constructor
};
类 X 的构造函数看起来像它首先初始化“b”,但它实际上是按声明顺序初始化的。这意味着它将首先初始化“a”。但是“a”被初始化为尚未初始化的“b”的值,所以“a”将得到一个垃圾值。
破坏与构造相反,因此元素以相反的顺序被破坏。
假设我们有 2 个成员,a
并且b
. b
取决于a
但a
不取决于b
。
当我们构造时,我们首先构造a
,现在它存在我们可以构造b
。当我们破坏时,如果我们a
先破坏,这将是一个问题,b
取决于它。但我们先破坏b
,确保完整性。
这是典型的。例如在群论中,倒数fg
是~g~f
(其中~f
的倒数是f
)
穿衣服的时候,先穿袜子,再穿鞋。脱衣服的时候先脱鞋,再脱袜子。
如果您的成员的构造函数之一抛出异常,这也可能是一个问题。然后所有已经正确构造的成员都必须按某种顺序销毁,因为没有类似于析构函数的初始化列表的东西。这个顺序是类声明中成员出现的相反顺序。一个例子:
#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!
进一步说,这样做的原因是数据元素的析构函数以其构造函数的相反顺序被调用。
请参阅 Steve Jessop 在Class component order of initialisation的评论