Является ли это поведение инициализации члена C++ корректным?

Предположим, у нас есть класс B, у которого есть member который по умолчанию инициализирован в 42. Этот класс знает, как напечатать значение своего member (он делает это в конструкторе):

struct B
{
  B() : member(42) { printMember(); }

  void printMember() const { std::cout << "value: " << member << std::endl; }

  int member;
};

Затем мы добавляем класс A который получает константную ссылку на B и просит B напечатать его значение:

struct A
{
  A(const B& b) { b.printMember(); }
};

Наконец, мы добавляем еще один класс Aggregate который агрегирует A и B Сложная часть является то, что объект типа a A объявляется перед тем объектом b типа B, а затем инициализируется с использованием (еще не действительная?) Ссылки на a b:

struct Aggregate
{
  A a;
  B b;

  Aggregate() : a(b) { }
};

Рассмотрим результат создания Aggregate (я добавил некоторые записи как в конструктор, так и в деструктор A и B) (попробуйте онлайн!):

a c'tor
value: 0
b c'tor
value: 42
b d'tor
a d'tor

Правильно ли я предполагаю, что неверно инициализировать a ссылкой на (еще не действительный) экземпляр b и что поэтому это неопределенное поведение?


Я в курсе порядка инициализации. Это то, что заставляет меня бороться. Я знаю, что b еще не сконструирован, но я также думаю, что знаю, что b будущий адрес может быть определен еще до того, как b сконструирован. Поэтому я предположил, что может быть какое-то правило, о котором я не знаю, которое позволяет компилятору инициализировать b членов по умолчанию перед созданием b или что-то в этом роде. (Было бы более очевидно, если бы первое распечатанное значение было бы чем-то, что выглядит случайным, а не 0 (значение по умолчанию int)).


Этот ответ помог мне понять, что мне нужно различать

  • привязка ссылки к неинициализированному объекту (который действителен) и
  • доступ по ссылке к неинициализированному объекту (который не определен)

Ответ 1

Да, вы правы, что это UB, но по другим причинам, чем просто сохранение ссылки на объект, который не был создан.

Построение учеников происходит в порядке их появления в классе. Хотя адрес B не собирается изменяться, и технически вы можете сохранить ссылку на него, как указывало @StoryTeller, вызов b.printMember() в конструкторе с b который еще не был создан, определенно является UB.

Ответ 2

Порядок инициализации членов класса приведен ниже.

Из стандарта CPP (N4713) соответствующая часть подсвечивается:

15.6.2 Инициализация баз и членов [class.base.init]...
13 В конструкторе без делегирования инициализация выполняется в следующем порядке:
(13.1). Во-первых, и только для конструктора самого производного класса (6.6.2) виртуальные базовые классы инициализируются в том порядке, в каком они появляются на первом переходе слева направо влево-вправо направленного ациклического графа базы классы, где "слева направо" - это порядок появления базовых классов в базовом-спецификационном списке производного класса.
(13.2). Затем прямые базовые классы инициализируются в порядке объявления, как они появляются в списке-спецификаторе-базе (независимо от порядка mem-инициализаторов).
(13.3). Затем нестатические элементы данных инициализируются в том порядке, в котором они были объявлены в определении класса (опять же, независимо от порядка mem-инициализаторов).
(13.4). Наконец, выполняется составная инструкция тела конструктора.
[Примечание. Приказ декларации уполномочен обеспечить, чтобы субобъекты базы и члена были уничтожены в обратном порядке инициализации. -End note]