
#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++ допускает сосуществование нескольких конструкторов. В каком же порядке разрушать части объекта, если порядок инициализации в разных конструкторах различен? Помнить, как именно был создан объект, может оказаться весьма дорого. Остаётся только одно — ввести для всех конструкторов строгий порядок инициализации, не связанный с их кодом.
Что и было сделано.
А чтобы не наступить случайно на эти грабли, лучше всегда описывать инициализацию в том же порядке, в каком декларируются члены класса.
Всем успехов!
upd: на это есть стандарт тут пункт 12.6.2 #5 стр 197.
upd2: или http://www.kuzbass.ru:8086/docs/isocpp/special.html#class.base.init (спасибо EntropiouS)