Читаем Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ полностью

На практике эти две копирующие функции часто имеют похожие реализации, и у вас может возникнуть соблазн избежать дублирования кода за счет вызова одной функции из другой. Такое стремление похвально, но вызов одной копирующей функции из другой – неверный путь.

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

Попытка выполнить обратную операцию – из конструктора копирования вызвать оператор присваивания – также бессмысленна. Конструктор инициализирует новые объекты, а оператор присваивания работает с уже существующими и инициализированными объектами. Выполнять присваивание объекту, находящемуся в процессе конструирования, – значит делать с еще не инициализированным объектом что-то такое, что имеет смысл только для инициализированного объекта. Нонсенс! Даже не пытайтесь.

Но если вы обнаружите, что ваш конструктор копирования и оператор присваивания содержат похожий код, попробуйте избежать дублирования, создав функцию-член, которую будут вызывать оба. Такая функция обычно делается закрытой и часто называется init. Эта стратегия представляет безопасный, испытанный способ избежать дублирования кода в конструкторах копирования и операторах присваивания.

Что следует помнить

• Копирующие функции должны гарантировать копирование всех членов-данных объекта и частей его базовых классов.

• Не пытайтесь реализовать одну из копирующих функций в терминах другой. Вместо этого поместите общую функциональность в третью функцию, которую вызовут обе.

Глава 3

Управление ресурсами

Ресурс – это нечто такое, что после использования должно быть возвращено системе. Если этого не сделать, случаются неприятности. В программах на C++ наиболее часто используемым ресурсом является динамическая память (если вы выделяете память и никогда ее не освобождаете, то получаете утечку памяти), но это лишь один из множества ресурсов, которыми нужно управлять. К другим часто используемым ресурсам относятся файловые дескрипторы, мьютексы, шрифты и кисти в графических интерфейсах пользователя (GUI), соединения с базой данных и сетевые сокеты. Независимо от вида ресурсов, важно, чтобы по окончании использования они были освобождены.

Попытки обеспечить это «вручную» представляют сложность при любых условиях, но если принять во внимание исключения, функции со многими путями возврата и приложения, модифицируемое программистами без отчетливого понимания последствий вносимых изменений, то становится ясно, что придумывать в каждом случае особый способ управления ресурсами нежелательно.

Эта глава начинается с прямого, базирующегося на объектах подхода к управлению ресурсами, построенного на имеющейся в C++ поддержке для конструкторов, деструкторов и операций копирования. Опыт показывает, что при дисциплинированном подходе можно исключить почти все проблемы с управлением ресурсами. Следующие далее правила посвящены исключительно управлению памятью. Каждое следующее правило уточняет предыдущие: объекты, управляющие памятью, должны делать это правильно.

Правило 13: Используйте объекты для управления ресурсами

Предположим, что мы работаем с библиотекой, моделирующей инвестиции (то есть акции, облигации и т. п.), и классы, представляющие разные виды инвестиций, наследуются от корневого класса Investment:


class Investment {...} // корневой класс иерархии

// типов инвестиций


Предположим далее, что библиотека предоставляет объекты, описывающие конкретные инвестиции, с помощью фабричной функции (см. правило 7):


Investment *createInvestment; // возвращает указатель на динамически

// распределенный объект в иерархии

// Investment: вызвавший клиент обязан

// удалить его (параметры для простоты

// опущены)


Как следует из комментария, пользователь, вызвавший createlnvestment, отвечает за удаление объекта, возвращенного этой функцией, по окончании его использования. Рассмотрим теперь функцию f, которая это делает:


void f

{

Investment *pInv = createInvestment; // вызвать фабричную функцию

... // использовать pInv

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже