MyTetra Share
Делитесь знаниями!
Порядок инициализации в конструкторах
Время создания: 28.05.2018 13:41
Текстовые метки: Си++, C++, конструктор, порядок инициализации, список инициализации
Раздел: Компьютер - Программирование - Язык C++ (Си++)
Запись: xintrea/mytetra_syncro/master/base/1527504092xscufwwdkw/text.html на raw.github.com

Порядок инициализации в конструкторах


Итак, вот небольшая программа на 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++ допускает сосуществование нескольких конструкторов. В каком же порядке разрушать части объекта, если порядок инициализации в разных конструкторах различен? Помнить, как именно был создан объект, может оказаться весьма дорого. Остаётся только одно — ввести для всех конструкторов строгий порядок инициализации, не связанный с их кодом.

Что и было сделано.

А чтобы не наступить случайно на эти грабли, лучше всегда описывать инициализацию в том же порядке, в каком декларируются члены класса.


Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования