В конструкторах использование инициализации вместо присваивания для установки значений переменных-членов предохраняет от ненужной работы времени выполнения при том же объеме вводимого исходного текста.
Конструкторы генерируют скрытый код инициализации. Рассмотрим следующий код:
class A {
string s1_, s2_;
public:
A() { s1_ = 'Hello, '; s2_ = 'world'; }
};
В действительности сгенерированный код конструктора выглядит так, как если бы вы написали:
А() : s1_(), s2_() { s1_ = 'Hello, '; s2_ = 'world'; }
To есть объекты, не инициализированные вами явно, автоматически инициализируются с использованием их конструкторов по умолчанию, после чего выполняется присваивание значений с использованием их операторов присваивания. Чаще всего операторы присваивания нетривиальных объектов выполняют немного больше работы, чем конструкторы, поскольку работают с уже созданными объектами.
Таким образом, инициализация переменных-членов в списке инициализации дает код, лучше выражающий ваши намерения и обычно более быстрый и меньшего размера:
А() : s1_('Hello, '), s2_('world ') { }
Эта методика не является преждевременной оптимизацией; это — избежание преждевременной пессимизации (см. рекомендацию 9).
Всегда выполняйте захват неуправляемого ресурса (например, выделение памяти оператором new
, результат которого не передается немедленно конструктору интеллектуального указателя) в теле конструктора, а не в списке инициализации (см. [Sutter02]). Конечно, лучше всего вообще не использовать таких небезопасных и не имеющих владельца ресурсов (см. рекомендацию 13).
49. Избегайте вызовов виртуальных функций в конструкторах и деструкторах
Внутри конструкторов и деструкторов виртуальные функции теряют виртуальность. Хуже того — все прямые или косвенные вызовы нереализованных чисто виртуальных функций из конструктора или деструктора приводят к неопределенному поведению. Если ваш дизайн требует виртуальной передачи в производный класс из конструктора или деструктора базового класса, следует воспользоваться иной методикой, например, постконструкторами.
В C++ полный объект конструируется по одному базовому классу за раз.
Пусть у нас есть базовый класс В
и класс D
, производный от B
. При создании объекта D
, когда выполняется конструктор В
, динамическим типом создаваемого объекта является B
. В частности, вызов виртуальной функции B::Fun
приведет к выполнению функции Fun
, определенной в классе В
, независимо от того, перекрывает ее класс D
или нет. И это хорошо, поскольку вызов функции-члена D
в тот момент, когда члены объекта D
еще не инициализированы, может привести к хаосу. Только после завершения выполнения конструктора В
выполняется тело конструктора D
и объект приобретает тип D
. В качестве эмпирического правила следует помнить, что в процессе конструирования В
нет никакого способа определить, является ли В
отдельным объектом или базовой частью некоторого иного производного объекта.
Кроме того, следует помнить, что вызов из конструктора чисто виртуальной функции, не имеющей определения, приводит к неопределенному поведению.
С другой стороны, в некоторых случаях дизайн требует использования 'постконструктора', т.е. виртуальной функции, которая должна быть вызвана после того, как полный объект оказывается сконструирован. Некоторые методики, применяемые для решения этой задачи, описаны в приводимых ниже ссылках. Вот (далеко не исчерпывающий) список возможных решений.
•
• bool
, который показывает, был уже вызван постконструктор или нет.
•
•
Ни одна из методик постконструирования не является идеальной. Наихудшее, что можно сделать, — это потребовать, чтобы пользователь класса вручную вызывал постконструктор. Однако даже наилучшие способы решения данной задачи требуют отличного от обычного синтаксиса конструирования объекта (что легко проверяется в процессе компиляции) и/или сотрудничества с авторами производных классов (что невозможно проверить в процессе компиляции).
class B { // Корень иерархии
protected:
В() {/*...*/ }
virtual void PostInitialize() // Вызывается сразу
{/*...*/} // после конструирования
publiс:
template<class T>
static shared_ptr<T> Create() // Интерфейс для
{ // создания объектов
shared_ptr<T> p(new T);
p->PostInitialize();
return p;
}
};
class D : public B { /* ... */ }; // Некоторый производный
// класс
shared_ptr<D> p = D::Create<D>(); // Создание объекта D
Этот не вполне надежный дизайн основан на ряде компромиссов.