Чтение онлайн

на главную - закладки

Жанры

Стандарты программирования на С++. 101 правило и рекомендация

Александреску Андрей

Шрифт:
Обсуждение

Конструкторы генерируют скрытый код инициализации. Рассмотрим следующий код:

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

49. Избегайте вызовов виртуальных функций в конструкторах и деструкторах

Резюме

Внутри конструкторов и деструкторов виртуальные функции теряют виртуальность. Хуже того — все прямые или косвенные вызовы нереализованных чисто виртуальных функций из конструктора или деструктора приводят к неопределенному поведению. Если ваш дизайн требует виртуальной передачи в производный класс из конструктора или деструктора базового класса, следует воспользоваться иной методикой, например, постконструкторами.

Обсуждение

В 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
; эти перегрузки могут даже быть шаблонами.

Поделиться:
Популярные книги

Долгий путь домой

Русич Антон
Вселенная EVE Online
Фантастика:
космическая фантастика
попаданцы
6.20
рейтинг книги
Долгий путь домой

Убивать чтобы жить 6

Бор Жорж
6. УЧЖ
Фантастика:
боевая фантастика
космическая фантастика
рпг
5.00
рейтинг книги
Убивать чтобы жить 6

Возвышение Меркурия. Книга 3

Кронос Александр
3. Меркурий
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия. Книга 3

Фиктивная жена

Шагаева Наталья
1. Братья Вертинские
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Фиктивная жена

Здравствуй, 1985-й

Иванов Дмитрий
2. Девяностые
Фантастика:
альтернативная история
5.25
рейтинг книги
Здравствуй, 1985-й

Барон Дубов 4

Карелин Сергей Витальевич
4. Его Дубейшество
Фантастика:
юмористическое фэнтези
аниме
сказочная фантастика
фэнтези
5.00
рейтинг книги
Барон Дубов 4

Лекарь для захватчика

Романова Елена
Фантастика:
попаданцы
историческое фэнтези
фэнтези
5.00
рейтинг книги
Лекарь для захватчика

Идеальный мир для Лекаря 24

Сапфир Олег
24. Лекарь
Фантастика:
городское фэнтези
попаданцы
5.00
рейтинг книги
Идеальный мир для Лекаря 24

Ваше Сиятельство 9

Моури Эрли
9. Ваше Сиятельство
Фантастика:
боевая фантастика
попаданцы
стимпанк
аниме
фэнтези
5.00
рейтинг книги
Ваше Сиятельство 9

Господин следователь

Шалашов Евгений Васильевич
1. Господин следователь
Детективы:
исторические детективы
5.00
рейтинг книги
Господин следователь

Академия проклятий. Книги 1 - 7

Звездная Елена
Академия Проклятий
Фантастика:
фэнтези
8.98
рейтинг книги
Академия проклятий. Книги 1 - 7

Полковник Гуров. Компиляция (сборник)

Макеев Алексей Викторович
Полковник Гуров
Детективы:
криминальные детективы
шпионские детективы
полицейские детективы
боевики
крутой детектив
5.00
рейтинг книги
Полковник Гуров. Компиляция (сборник)

Первый среди равных. Книга V

Бор Жорж
5. Первый среди Равных
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Первый среди равных. Книга V

Землянка для двух нагов

Софи Ирен
Фантастика:
космическая фантастика
5.00
рейтинг книги
Землянка для двух нагов