In Effective C++, it is said that data elements in the initialization list need to be listed in the order of their declaration. It is further said that the reas开发者_开发问答oning for this is that destructors for data elements get called in the reverse order of their constructors.
But I just don't see how this could be a problem...
Well consider the following:
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;
};
The second (commented out) constructor looks allright, but in fact only var2
will be initialized - var1
will be initialized first and it will be initialized with var2
that is not yet initialized at that point.
If you list initializers in the same order as member variables are listed in the class declaration risk of such errors becomes much lower.
The order of construction and destruction may be important when the members are also objects of some class that somehow depend on each other.
Consider a simple example:
class MyString {
public:
size_t s_length;
std::string s;
MyString(const char *str) : s(str), s_length(s.length()) {}
};
The intention in this example is that member s_length
holds the length of the stored string. This will not work however, because s_length
will be initialised before s
. So you call s.length
before the constructor of s
is executed!
For example if you have a class like this:
class X {
int a,b;
X(int i) : b(i),a(b) { } // Constructor
};
The constructor for class X looks like it initialises "b" first but it actually initialises in order of declaration. That means it will initialise "a" first. However "a" is initialised to the value of "b" which hasn't been initialised yet, so "a" will get a junk value.
Destruction is the reverse of construction, therefore elements are destructed in reverse order.
Let us say we have 2 members, a
and b
. b
depends on a
but a
does not depend on b
.
When we construct, we first construct a
and now it exists we can construct b
. When we destruct, if we destruct a
first this will be a problem as b
depends on it. But we destruct b
first and integrity is ensured.
This is typical. For example in group theory, the inverse of fg
is ~g~f
(where ~f
is the inverse of f
)
When you dress, you first put on socks and then you put on shoes. When you undress you first remove the shoes, then the socks.
It also could be a problem if one of the constructors of your members throws an exception. Then all members which were already properly constructed must be destructed in some order because there isn't something similar to initializer-lists for destructors. This order is the reverse order of appearance of the members in the class declaration. An example:
#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";
}
}
The output will be something like this:
A1::A1(int)
A2::A2(int)
B::B(int)
A2::~A2()
A1::~A1()
Exception!
It is further said that the reasoning for this is that destructors for data elements get called in the reverse order of their constructors.
See Steve Jessop's comment at Class component order of initialisation
精彩评论