Исходите из того, что члены и базовые классы инициализируются в случайном порядке
Многие неопытные программисты на Си++ избегают списков инициализации членов, как я полагаю, потому, что они выглядят так причудливо. Фактом является то, что большинство программ, которые их не используют, попросту некорректны. Возьмите, например, следующий код (определение строкового класса из листинга 7 со страницы 155):
class base
{
string s;
public:
base( const char *init_value );
}
//------------------------------
base::base( const char *init_value )
{
s = init_value;
}
Основной принцип такой: если у вас есть доступ к объекту, то он должен быть инициализирован. Так как поле s
видимо для конструктора base, то Си++ гарантирует, что оно инициализировано до окончания выполнения тела конструктора. Список инициализации членов является механизмом выбора выполняемого конструктора. Если вы его опускаете, то получите конструктор по умолчанию, у которого нет аргументов, или, как в случае рассматриваемого нами класса string, такой, аргументы которого получают значения по умолчанию. Следовательно, компилятор вначале проинициализирует s
пустой строкой, разместив односимвольную строку при помощи new и поместив в нее \0. Затем выполняется тело конструктора и вызывается функция string::operator=(). Эта функция освобождает только что размещенный буфер, размещает буфер большей длины и инициализирует его значением init_value. Ужасно много работы. Лучше сразу проинициализировать объект корректным начальным значением. Используйте:
base( const char *init_value ) : s(init_value)
{}
Теперь строка s
будет инициализирована правильно, и не нужен вызов operator=() для ее повторной инициализации.
Настоящее правило также применимо к базовым классам, доступным из конструктора производного класса, поэтому они должны инициализироваться до выполнения конструктора производного класса. Базовые классы инициализируются перед членами производного класса, потому что члены производного класса невидимы в базовом классе. Подведем итог - объекты инициализируются в следующем порядке:
·
Базовые классы в порядке объявления.
· Поля данных в порядке объявления.
Лишь затем выполняется конструктор производного класса. Одно последнее предостережение. Заметьте, что порядок объявления управляет порядком инициализации. Порядок, в котором элементы появляются в списке инициализации членов, является несущественным. Более того, порядок объявления не должен рассматриваться как неизменный. Например, вы можете изменить порядок, в котором объявлены поля данных. Рассмотрим следующее определение класса где-нибудь в заголовочном файле:
class wilma
{
int y;
int x;
public:
wilma( int ix );
};
Вот определение конструктора в файле .c:
wilma::wilma( int ix ) : y(ix * 10), x(y + 1)
{}
Теперь допустим, что какой-то сопровождающий программист переставит поля данных в алфавитном порядке, поменяв местами x и y. Этот конструктор больше не работает: поле x
инициализируется первым, потому что оно первое в определении класса, и инициализируется значением y+1, но поле y еще не инициализировалось.
Исправьте код, исключив расчет на определенный порядок инициализации:
wilma::wilma( int ix ) : y(ix * 10), x((ix *10) + 1)
{}