Можно ли использовать переменные-члены для инициализации других членов в списке инициализации?

Рассмотрим следующую (упрощенную) ситуацию:

class Foo
{
private:
    int evenA;
    int evenB;
    int evenSum;
public:
    Foo(int a, int b) : evenA(a-(a%2)), evenB(b-(b%2)), evenSum(evenA+evenB)
    {
    }
};

Когда я создаю Foo следующим образом:

Foo foo(1,3);

тогда evenA равно 0, evenB равно 2, но будет ли evenSum инициализирован равным 2?

Я попробовал это на моей текущей платформе (iOS), и, похоже, это работает, но я не уверен, является ли этот код переносимым.

Спасибо за вашу помощь!


person Pontomedon    schedule 23.05.2012    source источник
comment
Это один из опасных уголков C++.   -  person iammilind    schedule 23.05.2012
comment
Codepad — отличное место для проверки таких вещей: codepad.org/uFgZpkwN   -  person Agent_L    schedule 23.05.2012
comment
@Agent_L: Это не скажет вам, является ли код переносимым.   -  person Oliver Charlesworth    schedule 23.05.2012
comment
@OliCharlesworth нет, но иногда это покажет, если это не так.   -  person Agent_L    schedule 23.05.2012


Ответы (4)


Он является хорошо определенным и переносимым1, но потенциально подвержен ошибкам.

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

<ч> <под>1. Из [class.base.init] в стандарте (ах) С++:

В конструкторе без делегирования инициализация выполняется в следующем порядке:

  • Во-первых, и только для конструктора самого производного класса (1.8), виртуальные базовые классы инициализируются в том порядке, в котором они появляются при обходе в глубину слева направо ориентированного ациклического графа базовых классов, где «лево- вправо» — это порядок появления базовых классов в списке базовых спецификаторов производного класса.
  • Затем непосредственные базовые классы инициализируются в порядке объявления, как они появляются в списке спецификаторов базы (независимо от порядка инициализаторов памяти).
  • Затем нестатические данные-члены инициализируются в том порядке, в котором они были объявлены в определении класса (опять же, независимо от порядка мем-инициализаторов).
  • Наконец, выполняется составной оператор тела конструктора.

(Выделение мое.)

Далее в этом разделе стандарта приводится пример использования переменных-членов для инициализации других переменных-членов.

person Oliver Charlesworth    schedule 23.05.2012
comment
Итак, если у Foo есть базовый класс Bar (невиртуальное публичное наследование), и я добавляю конструктор Bars в список инициализации, он всегда будет выполняться перед всеми инициализаторами членов, даже если я помещу его в конец списка инициализации? - person Pontomedon; 23.05.2012
comment
@ Понтомедон: Да. Конструкторы базового класса всегда вызываются первыми (даже если их вообще нет в списке). - person Oliver Charlesworth; 23.05.2012

Да, если они уже построены. Только не забывайте, что порядок построения — это порядок объявлений в определении класса, не порядок инициализаторов в конструкторе. И что компилятор обычно не сообщает вам, используете ли вы переменную до ее создания. В вашем случае, например, если вы переместите evenSum в начало класса, у вас будет неопределенное поведение (поскольку его инициализатор использует неинициализированные члены), даже если в вашем конструкторе вы инициализируете evenA и evenB лексически перед evenSum.

person James Kanze    schedule 23.05.2012

Члены инициализируются в том порядке, в котором они объявлены в определении класса. Пока ваш список инициализаторов следует этому порядку, все должно быть в порядке.

person Alex Bakulin    schedule 23.05.2012

Это также скомпилировано без ошибок на g++ 4.0.3 (сейчас 6 лет).

Я уверен, что это будет нормально компилироваться на любом сравнительно новом компиляторе.

person Michael Slade    schedule 23.05.2012