Правила программирования на Си и Си++

       

Исходите из того, что члены и базовые классы инициализируются в случайном порядке


Многие неопытные программисты на Си++ избегают списков инициализации членов, как я полагаю, потому, что они выглядят так причудливо. Фактом является то, что большинство программ, которые их не используют, попросту некорректны. Возьмите, например, следующий код (определение строкового класса из листинга 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)
{}

Содержание раздела