Всегда ли список инициализации обрабатывается перед кодом конструктора?
Другими словами, будет ли следующий код всегда печатать <unknown>
, а сконструированный класс будет иметь "известный" как значение для source_
(если глобальная переменная something
равна true
) ?
class Foo {
std::string source_;
public:
Foo() : source_("<unknown>") {
std::cout << source_ << std::endl;
if(something){
source_ = "known";
}
}
};
3 ответа
Да, согласно C++11: 12.6.2 /10
(тот же раздел в C++14
, 15.6.2 /13
в C++17
):
В конструкторе без делегирования инициализация происходит в следующем порядке (выделено жирным шрифтом):
Во-первых, и только для конструктора самого производного класса (1.8) виртуальные базовые классы инициализируются в том порядке, в котором они появляются при обходе направленного ациклического графа базовых классов в глубину слева направо, где «left- to-right »- это порядок появления базовых классов в списке базовых спецификаторов производных классов.
Затем прямые базовые классы инициализируются в порядке объявления, как они появляются в списке-спецификаторах базы (независимо от порядка инициализаторов памяти).
Затем не статические члены данных инициализируются в порядке, в котором они были объявлены в определении класса (опять же, независимо от порядка mem-инициализаторов).
Наконец, выполняется составной оператор тела конструктора.
Основная причина использования списков инициализации - помочь компилятору в оптимизации. Списки инициализации для неосновных типов (т. Е. Объектов класса, а не int
, float
и т. Д.), Как правило, могут быть созданы на месте.
Если вы создаете объект, а затем назначаете его в конструкторе, это обычно приводит к созданию и уничтожению временных объектов, что неэффективно.
Init-списки могут избежать этого (если компилятор, конечно, готов, но большинство из них должно быть).
Следующая полная программа выведет 7, но это для конкретного компилятора (CygWin g ++), поэтому это не гарантирует такого поведения больше, чем образец в исходном вопросе.
Однако, как следует из ссылки в первом абзаце выше, стандарт действительно гарантирует это.
#include <iostream>
class Foo {
int x;
public:
Foo(): x(7) {
std::cout << x << std::endl;
}
};
int main (void) {
Foo foo;
return 0;
}
Да, C ++ конструирует все члены перед вызовом кода конструкции.
Как уже было сказано, списки инициализации полностью выполняются до входа в блок конструктора. Таким образом, совершенно безопасно использовать (инициализированные) члены в теле конструктора.
В принятом ответе вы сделали комментарий о необходимости ссылаться на аргументы конструктора, но не на переменные-члены внутри блока конструктора. Вы этого не сделаете.
Возможно, вы ошибочно приняли тот факт, что вы должны ссылаться на параметры, а не на атрибуты членов внутри списка инициализации. В качестве примера, учитывая класс X, который имеет два члена (a_ и b_) типа int, следующий конструктор может быть некорректно определен:
X::X( int a ) : a_( a ), b( a_*2 ) {}
Возможная проблема здесь в том, что построение элементов в списке инициализации зависит от порядка объявления в классе, а не от порядка, в котором вы вводите список инициализации. Если бы класс был определен как:
class X
{
public:
X( int a );
private:
int b_;
int a_;
};
Затем, независимо от того, как вы вводите список инициализации, факт в том, что b_ (a_ * 2) будет выполняться до инициализации a_, поскольку объявление членов сначала b_, а затем a_. Это создаст ошибку, поскольку ваш код считает (и, вероятно, зависит) от того, что b_ в два раза больше значения a_, а на самом деле b_ содержит мусор. Самое простое решение не относится к членам:
X::X( int a ) : a_( a ), b( a*2 ) {} // correct regardless of how X is declared
Как избежать этой ловушки - причина, по которой вам предлагается не использовать атрибуты членов как часть инициализации других членов.
Похожие вопросы
Новые вопросы
c++
C ++ - это язык программирования общего назначения. Первоначально он был разработан как расширение C и имеет аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде (который должен быть) скомпилирован с помощью компилятора C ++. Используйте тег для конкретной версии для вопросов, связанных с конкретной версией стандарта [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] или [C ++ 23] и т. Д. .