Правильный ответ - {мусор} {мусор} 5.
Список инициализации в конструкторе - вещь дельная, полезная и иногда без нее никуда. Но с ним есть один неприятный момент - все может пойти по шляпе, если не соблюдать одно правило. "Порядок инициализации полей в списке инициализации конструктора должен совпадать с порядком следования полей в описании класса". Но вот не все знают, что же будет, если это правило нарушить. Поэтому давайте разбираться.
Для начала приведу цитату из стандарта:
Теперь разжуем эту сухую писанину в мягкую кашицу для лучшего усвоения.
В начале инициализируются базы класса в порядке, определяемом древовидной структурой наследования и ее проходом в глубину слева-направо. Не думаю, что сейчас надо что-то больше пояснять, как и про инициализацию статических полей. Ориентировочно 3.245 лайка на этот пост хватит, чтобы мы начали готовить про эти темы посты.
Далее инициализируются нестатические поля класса в порядке, в котором они объявлены в определении класса, и который никак не зависит от списка инициализации конструктора.
Ну и далее выполняется само тело конструктора.
Какие из этого выводы?
Самый главный - поведение абсолютно определено стандартом. Здесь нет никакого UB! Список инициализации лишь задает способ инициализации, но никак не влияет на порядок. Порядк определяется только порядком следования полей в описании класса(сверху вниз).
То есть в нашем примере в начале будет инициализировать
Никто нам не может запретить писать список инициализации в том порядке, в котором мы хотим. Да и непонятно, как это сделать. С точки зрения С++, код вполне корректен и может работать. Просто от этого будут последствия, но вполне предсказуемые.
Благо, к нам на помощь приходят компиляторы, которые аккуратно подскажут нам, что мы делаемчухню возможно не то, что хотим. Скорее всего компилятор вам сам кинет ворнинг, что переменная
Также вы можете(должны?) добавить флаг компиляции, чтобы превращать все предупреждения в ошибки. Тогда вы точно ничего важного не пропустите. Как говорится, флаг
Можете, кстати, поделиться в комментах историями, как у вас на проектах не стоял этот флаг, рекордным количеством неисправленных ворнингов и последствиями пренебрежением предупредпреждений.
Еще есть нюансы с default member initializer и его сочетанием со списком инициализации конструктора. Короче, много нюансов, как и во всех плюсах)
В один пост все все равно не влезет. Будем разделять и властвовать!
Devide et empera. Stay cool.
#cppcore
Список инициализации в конструкторе - вещь дельная, полезная и иногда без нее никуда. Но с ним есть один неприятный момент - все может пойти по шляпе, если не соблюдать одно правило. "Порядок инициализации полей в списке инициализации конструктора должен совпадать с порядком следования полей в описании класса". Но вот не все знают, что же будет, если это правило нарушить. Поэтому давайте разбираться.
Для начала приведу цитату из стандарта:
In a non-delegating constructor, initialization proceeds in the following order:
- First, and only for the constructor of the most derived class, virtual base classes
are initialized in the order they appear on a depth-first left-to-right traversal of the
directed acyclic graph of base classes, where “left-to-right” is the order of appearance
of the base classes in the derived class base-specifier-list.
- Then, direct base classes are initialized in declaration order as they appear in the
base-specifier-list(regardless of the order of the mem-initializer)
- Then, non-static data members are initialized in the order they were declared in the
class definition (again regardless of the order of the mem-initializer()
- Finally, the compound-statement of the constructor body is executed.
Теперь разжуем эту сухую писанину в мягкую кашицу для лучшего усвоения.
В начале инициализируются базы класса в порядке, определяемом древовидной структурой наследования и ее проходом в глубину слева-направо. Не думаю, что сейчас надо что-то больше пояснять, как и про инициализацию статических полей. Ориентировочно 3.245 лайка на этот пост хватит, чтобы мы начали готовить про эти темы посты.
Далее инициализируются нестатические поля класса в порядке, в котором они объявлены в определении класса, и который никак не зависит от списка инициализации конструктора.
Ну и далее выполняется само тело конструктора.
Какие из этого выводы?
Самый главный - поведение абсолютно определено стандартом. Здесь нет никакого UB! Список инициализации лишь задает способ инициализации, но никак не влияет на порядок. Порядк определяется только порядком следования полей в описании класса(сверху вниз).
То есть в нашем примере в начале будет инициализировать
b
, потом a
и, наконец, c
. И не смотря на то, что в списке инициализации c
стоит первым, на самом деле его инициализация будет проходить последней. А так как все остальные поля зависят от значения c
и инициализируются раньше него, то в них будет содержаться мусор.Никто нам не может запретить писать список инициализации в том порядке, в котором мы хотим. Да и непонятно, как это сделать. С точки зрения С++, код вполне корректен и может работать. Просто от этого будут последствия, но вполне предсказуемые.
Благо, к нам на помощь приходят компиляторы, которые аккуратно подскажут нам, что мы делаем
c
используется неинициализированной. Чтобы точно заставить компилятор это сделать, добавьте опцию -Wall
. Подробнее про ворнинги в этом цикле статей.Также вы можете(должны?) добавить флаг компиляции, чтобы превращать все предупреждения в ошибки. Тогда вы точно ничего важного не пропустите. Как говорится, флаг
-Werror
вам в руки!Можете, кстати, поделиться в комментах историями, как у вас на проектах не стоял этот флаг, рекордным количеством неисправленных ворнингов и последствиями пренебрежением предупредпреждений.
Еще есть нюансы с default member initializer и его сочетанием со списком инициализации конструктора. Короче, много нюансов, как и во всех плюсах)
В один пост все все равно не влезет. Будем разделять и властвовать!
Devide et empera. Stay cool.
#cppcore