Эта глава начинается с прямого, базирующегося на объектах подхода к управлению ресурсами, построенного на имеющейся в C++ поддержке для конструкторов, деструкторов и операций копирования. Опыт показывает, что при дисциплинированном подходе можно исключить почти все проблемы с управлением ресурсами. Следующие далее правила посвящены исключительно управлению памятью. Каждое следующее правило уточняет предыдущие: объекты, управляющие памятью, должны делать это правильно.
Правило 13: Используйте объекты для управления ресурсами
Предположим, что мы работаем с библиотекой, моделирующей инвестиции (то есть акции, облигации и т. п.), и классы, представляющие разные виды инвестиций, наследуются от корневого класса Investment:
class Investment {...} // корневой класс иерархии
// типов инвестиций
Предположим далее, что библиотека предоставляет объекты, описывающие конкретные инвестиции, с помощью фабричной функции (см. правило 7):
Investment *createInvestment(); // возвращает указатель на динамически
// распределенный объект в иерархии
// Investment: вызвавший клиент обязан
// удалить его (параметры для простоты
// опущены)
Как следует из комментария, пользователь, вызвавший createlnvestment, отвечает за удаление объекта, возвращенного этой функцией, по окончании его использования. Рассмотрим теперь функцию f, которая это делает:
void f()
{
Investment *pInv = createInvestment(); // вызвать фабричную функцию
... // использовать pInv
delete pInv; // освободить память, занятую
} // объектом
Выглядит хорошо, но есть несколько случаев, когда f не удастся удалить объект инвестиций, полученный от createlnvestment. Где-нибудь внутри непоказанной части функции может встретиться предложение return. Если такой возврат будет выполнен, то управление никогда не достигнет оператора delete. Похожая ситуация может случиться, если вызов createlnvestment и delete поместить в в цикл, и этот цикл будет прерван в результате выполнения goto или continue. И наконец, некоторые предложения внутри части, обозначенной «…», могут возбудить исключение. И в этом случае управление не дойдет до оператора delete. Независимо от того, почему delete будет пропущен, мы потеряем не только память, выделенную для объекта Investment, но и все ресурсы, которые он захватил.
Конечно, тщательное программирование может предотвратить ошибки подобного рода, но подумайте о том, как может измениться код со временем. При сопровождении программы кто-то может добавить предложение return или continue, не вполне понимая последствий своих действий для стратегии управления ресурсами, реализованной в данной функции. Хуже того, часть «…» функции f может вызвать функцию, которая никогда не возбуждала исключений, но начнет это делать после некоторого «усовершенствования». То есть полагаться на то, что f всегда доберется до своего оператора delete, просто нельзя.
Чтобы обеспечить освобождение ресурса, возвращенного createlnvestment, нам нужно инкапсулировать ресурс внутри объекта, чей деструктор автоматически освободит его, когда управление покинет функцию f. Фактически это половина идеи дела: заключая ресурс в объект, мы можем положиться на автоматический вызов деструкторов C++, чтобы гарантировать их освобождение. (Вторую половину мы обсудим чуть ниже.)
Многие ресурсы динамически выделяются из «кучи», используются внутри одного блока или функции и должны быть освобождены, когда управление покидает этот блок или функцию. Для таких ситуаций предназначен класс стандартной библиотеки auto_ptr. Класс auto_ptr описывает объект, подобный указателю (интеллектуальный указатель), чей деструктор автоматически вызывает delete для того, на что он указывает. Вот как использовать auto_ptr для предотвращения потенциальной опасности утечки ресурсов в нашей функции f:
void f()
{
std::auto_ptr pInv(createInvestment()); // вызов фабричной
// функции
... // использование pInv как раньше
} // автоматическое удаление pInv
// деструктором auto_ptr
Этот простой пример демонстрирует два наиболее существенных аспекта применения объектов для управления ресурсами:
• Ресурс захватывается и сразу преобразуется объект, управлящий им.В приведенном примере ресурс, возвращенный функцией createInvestment, используется для инициализации auto_ptr, который будет им управлять. Фактически идею использования объектов для управления ресурсами часто называют Получение Ресурса Есть Инициализация (Resource Acquisition Is Initialization – RAII), поскольку нередко приходится получать ресурс и инициализировать объект управления ресурсом в одном и том же предложении. Иногда полученные ресурсы присваиваются управляющему объекту вместо инициализации, но в любом случае каждый ресурс сразу после получения преобразуется в управляющий им объект.
Читать дальше
Конец ознакомительного отрывка
Купить книгу