В конструкторах предпочитайте инициализацию присваиванию
Резюме
В конструкторах использование инициализации вместо присваивания для установки значений переменных-членов предохраняет от ненужной работы времени выполнения при том же объеме вводимого исходного текста.
Обсуждение
Конструкторы генерируют скрытый код инициализации. Рассмотрим следующий код: 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).
Ссылки
[Dewhurst03] §51, §59 • [Keffer95] pp.13-14 • [Meyers97] §12 • [Murray93] §2.1.31 • [Sutter00] §8, §47 • [Sutter02] §18
Избегайте вызовов виртуальных функций в конструкторах и деструкторах
Резюме
Внутри конструкторов и деструкторов виртуальные функции теряют виртуальность. Хуже того — все прямые или косвенные вызовы нереализованных чисто виртуальных функций из конструктора или деструктора приводят к неопределенному поведению. Если ваш дизайн требует виртуальной передачи в производный класс из конструктора или деструктора базового класса, следует воспользоваться иной методикой, например, постконструкторами.
Обсуждение
В C++ полный объект конструируется по одному базовому классу за раз. Пусть у нас есть базовый класс В и класс D, производный от B. При создании объекта D, когда выполняется конструктор В, динамическим типом создаваемого объекта является B. В частности, вызов виртуальной функции B::Fun приведет к выполнению функции Fun, определенной в классе В, независимо от того, перекрывает ее класс D или нет. И это хорошо, поскольку вызов функции-члена D в тот момент, когда члены объекта D еще не инициализированы, может привести к хаосу. Только после завершения выполнения конструктора В выполняется тело конструктора D и объект приобретает тип D. В качестве эмпирического правила следует помнить, что в процессе конструирования В нет никакого способа определить, является ли В отдельным объектом или базовой частью некоторого иного производного объекта. Кроме того, следует помнить, что вызов из конструктора чисто виртуальной функции, не имеющей определения, приводит к неопределенному поведению. С другой стороны, в некоторых случаях дизайн требует использования "постконструктора", т.е. виртуальной функции, которая должна быть вызвана после того, как полный объект оказывается сконструирован. Некоторые методики, применяемые для решения этой задачи, описаны в приводимых ниже ссылках. Вот (далеко не исчерпывающий) список возможных решений. • Перекладывание ответственности. Можно просто документировать необходимость вызова в пользовательском коде постконструктора после создания объекта. • Отложенная постинициализация. Можно выполнять постинициализацию при первом вызове функции-члена, для чего использовать в базовом классе флаг типа bool, который показывает, был уже вызван постконструктор или нет. • Использование семантики базового класса. Правила языка требуют, чтобы конструктор наиболее производного класса определял, какой из конструкторов базовых классов будет вызван. Вы можете использовать это правило в своих целях (см. [Taligent94]). • Использование функции-фабрики. Таким способом вы можете легко обеспечить принудительный вызов функции-постконструктора (см. примеры к данной рекомендации). Ни одна из методик постконструирования не является идеальной. Наихудшее, что можно сделать, — это потребовать, чтобы пользователь класса вручную вызывал постконструктор. Однако даже наилучшие способы решения данной задачи требуют отличного от обычного синтаксиса конструирования объекта (что легко проверяется в процессе компиляции) и/или сотрудничества с авторами производных классов (что невозможно проверить в процессе компиляции).
Примеры
Пример. Использование функции-фабрики для принудительного вызова постконструктора. Рассмотрим следующий код: 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 Этот не вполне надежный дизайн основан на ряде компромиссов. • Производные классы, такие как D, не должны иметь открытых конструкторов. В противном случае пользователи D смогут создавать объекты D, для которых не будет вызываться функция PostInitialize. • Создание объекта использует оператор new, который, однако, может быть перекрыт классом В (см. рекомендации 45 и 46). • Класс D обязательно должен определить конструктор с теми же параметрами, что и у конструктора класса B. Смягчить проблему может наличие нескольких перегрузок функции Create; эти перегрузки могут даже быть шаблонами. • Если перечисленные требования удовлетворены, данный дизайн гарантирует, что функция PostInitialize будет вызвана для любого полностью сконструированного объекта класса, производного от B. Функция PostInitialize не обязательно должна быть виртуальной; однако она может свободно вызывать виртуальные функции.
Ссылки
[Alexandrescu01] §3 • [Boost] • [Dewhurst03] §75 • [Meyers97] §46 • [Stroustrup00] §15.4.3 • [Taligent94]
Популярное: Почему стероиды повышают давление?: Основных причин три... Генезис конфликтологии как науки в древней Греции: Для уяснения предыстории конфликтологии существенное значение имеет обращение к античной... Как вы ведете себя при стрессе?: Вы можете самостоятельно управлять стрессом! Каждый из нас имеет право и возможность уменьшить его воздействие на нас... Как распознать напряжение: Говоря о мышечном напряжении, мы в первую очередь имеем в виду мускулы, прикрепленные к костям ... ![]() ©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (395)
|
Почему 1285321 студент выбрали МегаОбучалку... Система поиска информации Мобильная версия сайта Удобная навигация Нет шокирующей рекламы |