8.3. Использование конструкторов и деструкторов для управления ресурсами (RAII)
8.3. Использование конструкторов и деструкторов для управления ресурсами (RAII)
Проблема
Для класса, представляющего некоторый ресурс, требуется использовать конструктор для получения этого ресурса и деструктор для его освобождения. Эта методика часто называется «получение ресурсов как инициализация» (resource acquisition is initialization — RAII).
Решение
Выделите или получите ресурс в конструкторе и освободите этот ресурс в деструкторе. Это снизит объем кода, который пользователь класса должен будет написать для обработки исключений. Простая иллюстрация этой методики показана в примере 8.3.
Пример 8.3. Использование конструкторов и деструкторов
#include <iostream>
#include <string>
using namespace std;
class Socket {
public:
Socket(const string& hostname) {}
};
class HttpRequest {
public:
HttpRequest(const string& hostname) :
sock_(new Socket(hostname)) {}
void send(string soapMsg) {sock << soapMsg;}
~HttpRequest() {delete sock_;}
private:
Socket* sock_;
};
void sendMyData(string soapMsg, string host) {
HttpRequest req(host);
req.send(soapMsg);
// Здесь делать ничего не требуется, так как когда req выходит
// за диапазон, все очищается.
}
int main() {
string s = "xml";
sendMyData(s, "www.oreilly.com");
}
Обсуждение
Гарантии, даваемые конструкторами и деструкторами, представляют собой удобный способ заставить компьютер выполнить всю очистку за вас. Обычно инициализация объекта и выделение используемых ресурсов производится в конструкторе, а очистка — в деструкторе. Это нормально. Но программисты имеют склонность использовать последовательность событий «создание-открытие-использование-закрытие», когда пользователю класса требуется выполнять явные открытия и закрытия ресурсов. Класс файла является хорошим примером.
Примерно так звучит обычный аргумент в пользу RAII. Я легко мог бы создать в примере 8.3 свой класс HttpRequest, который заставил бы пользователя выполнить несколько больше работы. Например:
class HttpRequest {
public:
HttpRequest();
void open(const std::string& hostname);
void send(std::string soapMsg);
void close();
~HttpRequest();
private:
Socket* sock_;
};
При таком подходе соответствующая версия sendMyData может выглядеть так.
void sendMyData(std::string soapMsg, std::string host) {
HttpRequest req;
try {
req.open();
req.send(soapMsg);
req.close();
} catch (std::exception& e) {
req.close(); // Do something useful...
}
}
Здесь требуется выполнить больше работы без каких бы то ни было преимуществ. Этот дизайн заставляет пользователя писать больше кода и работать с исключениями, очищая ваш класс (при условии, что в деструкторе close не вызывается).
Подход RAII имеет широкое применение, особенно когда требуется гарантировать, что при выбрасывании исключения будет выполнен «откат» каких-либо действий, позволяя при этом не загромождать код бесконечными try/catch. Рассмотрим настольное приложение, которое в процессе выполнения какой-либо работы отображает в строке состояния или заголовка сообщение.
void MyWindow : thisTakesALongTime() {
StatusBarMessage("Copying files...");
// ...
}
Все, что класс StatusBarMessage должен сделать, — это использовать информацию о статусе для обновления соответствующего окна при создании и вернуть его первоначальное состояние при удалении. Вот ключевой момент: если функция завершает работу или выбрасывается исключение, StatusBarMessage все равно выполнит работу. Компилятор гарантирует, что при выходе из области видимости стековой переменной для нее будет вызван ее деструктор. Без этого подхода автор thisTakesALongTime должен был бы принять во внимание все пути передачи управления, чтобы неверное сообщение не осталось в окне при неудачном завершении операции, ее отмене пользователем и т.п. И снова повторю, что этот подход приводит к уменьшению кода и снижению числа ошибок автора вызывающего кода.
RAII не является панацеей, но если вы его еще не использовали, то вы, скорее всего, найдете немало возможностей для его применения. Еще одним хорошим примером является блокировка. При использовании RAII для управления блокировками ресурсов, таких как потоки, объекты пулов, сетевые соединения и т.п., этот подход позволяет создавать более надежный код меньшего размера. На самом деле именно так многопоточная библиотека Boost реализует блокировки, делая программирование пользовательской части более простым. За обсуждением библиотеки Boost Threads обратитесь к главе 12.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Использование утилит управления сценариями запуска
Использование утилит управления сценариями запуска Некоторые дистрибутивные пакеты включают специальные утилиты, которые упрощают управление сценариями запуска. Пользуясь этими утилитами, вы уменьшаете риск неправильно задать имя сценария. Так, например, изменяя
8.6. Управление ресурсами
8.6. Управление ресурсами В этом разделе мы рассмотрим только один аспект управления ресурсами: как сэкономить тот или иной ресурс, точнее, как поступить в случае, если какого-то ресурса недостаточно. Основными ресурсами компьютера являются память и дисковое пространство.
Использование сторонних систем управления контентом (CMS)
Использование сторонних систем управления контентом (CMS) Количество сторонних универсальных CMS, на которых будут собираться сайты, возрастет. Это вызвано тем, что многие средние студии имеют доморощенные наработки, не отвечающие современным требованиям по
Управление ресурсами
Управление ресурсами Еще одна проблема поддержки нескольких интерфейсов из одного объекта становится яснее, если исследовать схему использования клиентом метода DynamicCast. Рассмотрим следующую клиентскую программу:void f(void){IFastString *pfs = 0;IPersistentObject *ppo = 0;pfs = CreateFastString(«Feed BOB»);if
Использование представлений для управления доступом к данным
Использование представлений для управления доступом к данным Представление (view) — это, по сути, определение запроса, хранящегося в базе данных. Оно подобно определению запроса в базах данных Microsoft Jet, однако отличается местом хранения: располагается в базе данных и
Использование стандартных свойств элемента управления
Использование стандартных свойств элемента управления Многие элементы управления обладают стандартными свойствами, к которым относится свойство Value.В случае текстового поля вы не должны явно указывать, какое свойство используется - Value или Text - для настройки и
Использование элементов управления ActiveX
Использование элементов управления ActiveX Вопреки сложившейся репутации программного империалиста, Microsoft стремится сделать свои средства разработки полностью "открытыми". Основываясь на спецификациях ActiveX, любой программист может создавать новые элементы управления,
Использование элементов управления ActiveX в программах
Использование элементов управления ActiveX в программах Добавив элемент управления ActiveX в панель Toolbox, вы можете добавлять его в свои формы точно так же, как стандартные элементы управления VBA. Правда, чтобы заставить элемент управления делать что-нибудь полезное, нужно
Правило 13: Используйте объекты для управления ресурсами
Правило 13: Используйте объекты для управления ресурсами Предположим, что мы работаем с библиотекой, моделирующей инвестиции (то есть акции, облигации и т. п.), и классы, представляющие разные виды инвестиций, наследуются от корневого класса Investment:class Investment {...} // корневой
11.1.1. Применение нескольких конструкторов
11.1.1. Применение нескольких конструкторов В Ruby нет «настоящих» конструкторов, как в C++ или в Java. Сама идея, конечно, никуда не делась, поскольку объекты необходимо создавать и инициализировать, но реализация выглядит иначе.В Ruby каждый класс имеет метод класса new, который
Роль конструкторов
Роль конструкторов До сих пор объекты HelloClass строились с помощью конструктора, заданного по умолчанию, который, по определению, не имеет аргументов. Каждый класс C# автоматически снабжается типовым конструктором, который вы можете при необходимости переопределить. Этот
Определение конструкторов типов
Определение конструкторов типов Система CTS (общая система типов) поддерживает конструкторы как уровня экземпляра, так и уровня класса (статические конструкторы). В терминах CIL для конструкторов уровня экземпляра используется лексема .ctor, а для статических конструкторов
ГЛАВА 21. Использование элементов управления Windows Forms
ГЛАВА 21. Использование элементов управления Windows Forms Эта глава представляет собой краткое руководство по использованию элементов управления, определенных в пространстве имен System.Windows.Forms. В главе 19 вы уже имели возможность поработать с некоторыми элементами управления,
18.5.3. Порядок вызова конструкторов и деструкторов
18.5.3. Порядок вызова конструкторов и деструкторов Виртуальные базовые классы всегда конструируются перед невиртуальными, вне зависимости от их расположения в иерархии наследования. Например, в приведенной иерархии у класса TeddyBear (плюшевый мишка) есть два виртуальных
19.2.5. Раскрутка стека и вызов деструкторов
19.2.5. Раскрутка стека и вызов деструкторов Когда возбуждается исключение, поиск его catch-обработчика – раскрутка стека – начинается с функции, возбудившей исключение, и продолжается вверх по цепочке вложенных вызовов (см. раздел 11.3).Во время раскрутки поочередно
Наследование конструкторов
Наследование конструкторов Правила наследования конструкторов - достаточно сложные. В разных языках программирования приняты разные решения на этот счет. В частности, в Delphi Object Pascal все конструкторы наследуются. В .NET, напротив, конструкторы не наследуются. Причина такого