Порядок инициализации в конструкторах
Итак, вот небольшая программа на C++:
#include <iostream>
class A {
private:
int a;
int b;
public:
A(int x) : b(x), a(b) {}
void dump() {
std::cout << "a=" << a << " b=" << b << std::endl;
}
};
int main() {
A a(42);
a.dump();
return 0;
}
Если вы считаете, что она выдаст
a=42 b=42
То вы обманываетесь, она выдаст что-то вроде
a=4379 b=42
Это произойдёт потому, что компилятор будет инициализировать переменные не в том порядке, в котором они перечислены в строке:
A(int x) : b(x), a(b)
Сперва будет инициализирована переменная «a», и лишь потом переменная «b». Так как на момент инициализации «a», переменная «b» ещё имеет неопределённое значение, то и «a» получит неопределённое значение.
Ситуация становится ещё драматичней, если представить, что «a» и «b» не просто int-ы, а некие сложные объекты, у которых, скажем, параметры конструктора определяют количество выделяемой памяти. Тогда грабли могут ударить в лоб очень сильно.
А в каком же порядке идёт инициализация?
На самом деле, порядок инициализации никак не зависит от порядка в строке
A(int x) : b(x), a(b)
Всё определяется порядком деклараций:
int a;
int b;
Если переставить эти две строчки местами, то и порядок инициализации изменится, и конструктор станет работать правильно.
Вы можете убедиться в этом, поигравшись с вот таким примером
#include <iostream>
class S {
private:
int data;
public:
S(int x) {
std::cout << "S(int x) x=" << x << std::endl;
data = x;
}
S(S& x) {
std::cout << "S(S& x) x.data=" << x.data << std::endl;
data = x.data;
}
int dump() {
return data;
}
~S() {
std::cout << "~S() x.data=" << this->data << std::endl;
}
};
class A {
private:
S a; // попробуйте переставить местами
S b; // эти две декларации
public:
A(int x) : b(x), a(b) {}
void dump() {
std::cout << "a=" << a.dump() << " b=" << b.dump() << std::endl;
}
};
int main() {
A a(1);
a.dump();
return 0;
}
У меня но выдал вот такой результат:
S(S& x) x.data=134515845
S(int x) x=1
a=134515845 b=1
~S() x.data=1
~S() x.data=134515845
Обратите внимание, что сперва был выполнен конструктор «S(S& x)». Если же переставить местам декларации, то всё будет работать правильно.
Что это? Баг в С++?
Нет, конечно!
Объяснение
Дело в том, что при удалении объекта, все разрушительные действия должны выполняться в порядке, строго противоположном, порядку конструирования. Вместе с тем, C++ допускает сосуществование нескольких конструкторов. В каком же порядке разрушать части объекта, если порядок инициализации в разных конструкторах различен? Помнить, как именно был создан объект, может оказаться весьма дорого. Остаётся только одно — ввести для всех конструкторов строгий порядок инициализации, не связанный с их кодом.
Что и было сделано.
А чтобы не наступить случайно на эти грабли, лучше всегда описывать инициализацию в том же порядке, в каком декларируются члены класса.