Обучающие курсы:

Обучение профессии "Разработчик C#" + стажировка в Mail.ru
Обучение профессии "Разработчик Python" + трудоустройство
Обучение профессии "Веб-разработчик" + стажировка в Mail.ru


Главная страница
Библиотека (скачать книги)
Скачать софт
Введение в программирование
Стандарты для C++
Уроки по C#
Уроки по Python
HTML
Веб-дизайн
Ассемблер в среде Windows
ActiveX
Javascript
Общее о Линукс
Линукс - подробно
Линукс - новое
Delphi
Паскаль для начинающих
Турбопаскаль
Новости
Партнеры
Наши предложения
Архив новостей







Конструкторы, деструкторы и копирование

Если стандарт привел вас к обрыву, это еще не значит, что вы должны прыгнуть с него.
— Норман Даймонд (Norman Diamond)

О Большой Четверке специальных функций было сказано достаточно, чтобы вы не удивлялись тому, что им посвящен отдельный раздел. Здесь собраны знания и практика, связанные с конструкторами по умолчанию, копирующими конструкторами, копирующим присваиванием и деструкторами.

Одна из причин, по которым при работе с этими функциями следует быть особенно внимательными, заключается в том, что если вы дадите компилятору хотя бы полшанса — он тут же напишет эти функции за вас. Еще одна причина состоит в том, что C++ по умолчанию рассматривает классы как типы-значения, но далеко не все типы именно таковы (см. рекомендацию 32). Надо отчетливо понимать, когда следует писать (или запрещать) эти специальные функции явно, и следовать правилам и рекомендациям из этого раздела — это поможет вам в написании корректного, расширяемого и безопасного кода.

В этом разделе мы считаем наиболее значимой рекомендацию 51 — "Деструкторы, функции освобождения ресурсов и обмена не ошибаются".

47. Определяйте и инициализируйте переменные-члены в одном порядке

Резюме

Переменные-члены всегда инициализируются в том порядке, в котором они объявлены при определении класса; порядок их упоминания в списке инициализации конструктора игнорируется. Убедитесь, что в коде конструктора указан тот же порядок, что и в определении класса.

Обсуждение

Рассмотрим следующий код:

class Employee {
    string email_, firstName_, lastName_;
public:
    Employee( const char* firstName, const char* lastName )
        : firstName_(firstName), lastName_(lastName)
        , email_(firstName_+"."+lastName_+"@acme.com") {}
};

Этот код содержит ошибку, столь же неприятную, сколь и трудно обнаруживаемую. Поскольку член email_ объявлен в определении класса до first_ и last_, он будет инициализирован первым и будет пытаться использовать еще не инициализированные поля. Более того, если определение конструктора находится в отдельном файле, то выявить такое удаленное влияние порядка объявления переменных-членов класса на корректность конструктора окажется еще труднее.

Эта особенность языка обусловлена необходимостью гарантировать единый порядок уничтожения членов; в противном случае деструктор был бы должен уничтожать объекты в разном порядке, в зависимости от того, в каком именно порядке конструктор создавал их. Накладные расходы, необходимые для решения этой проблемы, признаны неприемлемыми.

Решение заключается в том, чтобы всегда писать инициализаторы членов в том же порядке, в котором эти члены объявлены в классе. В этом случае сразу становятся очевидными все некорректные зависимости между членами. Еще лучше полностью избегать зависимости инициализации одного члена от других.

Многие компиляторы (но не все) выдают предупреждение при нарушении этого правила.

48. В конструкторах предпочитайте инициализацию присваиванию

Резюме

В конструкторах использование инициализации вместо присваивания для установки значений переменных-членов предохраняет от ненужной работы времени выполнения при том же объеме вводимого исходного текста.

Обсуждение

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

class A {
    string sl_, s2_;
public:
    A() { sl_ = "Hello, "; s2_ = "world"; }
};

В действительности сгенерированный код конструктора выглядит так, как если бы вы написали:

А() : sl_(), s2_() { s1 = "Hello, "; s2_ = "world"; }

To есть объекты, не инициализированные вами явно, автоматически инициализируются с использованием их конструкторов по умолчанию, после чего выполняется присваивание значений с использованием их операторов присваивания. Чаще всего операторы присваивания нетривиальных объектов выполняют немного больше работы, чем конструкторы, поскольку работают с уже созданными объектами.

Таким образом, инициализация переменных-членов в списке инициализации дает код, лучше выражающий ваши намерения и обычно более быстрый и меньшего размера:

А() : sl_("Hello, "), s2_("world") { }

Эта методика не является преждевременной оптимизацией; это — избежание преждевременной пессимизации (см. рекомендацию 9).

Исключения

Всегда выполняйте захват неуправляемого ресурса (например, выделение памяти оператором new, результат которого не передается немедленно конструктору интеллектуального указателя) в теле конструктора, а не в списке инициализации. Конечно, лучше всего вообще не использовать таких небезопасных и не имеющих владельца ресурсов (см. рекомендацию 13).



 
 

Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru