Работать с функциями, представляющими такую гарантию, проще, чем с функциями, которые дают только базовую гарантию, потому что после их вызова может быть только два состояния программы: то, которое ожидается в результате ее успешного завершения, и то, которое было до ее вызова. Напротив, если исключение возникает в функции, представляющей только базовую гарантию, то программа может оказаться в любом корректном состоянии.
• Функции, предоставляющие гарантию отсутствия исключений,обещают никогда не возбуждать исключений, потому что всегда делают то, что должны делать. Все операции над встроенными типами (например, целыми, указателями и т. п.) обеспечивают такую гарантию. Это основной строительный блок безопасного относительно исключений кода. Разумно предположить, что функции с пустой спецификацией исключений не возбуждают их, но это не всегда так. Например, рассмотрим следующую функцию:
int doSomething() throw(); // обратите внимание на пустую
// спецификацию исключений
Это объявление не говорит о том, что doSomething никогда не возбуждает исключений. Утверждается лишь, что если doSomething возбудит исключение, значит, произошла серьезная ошибка и должна быть вызвана функция unexpected [3]. Фактически doSomething может вообще не представлять никаких гарантий относительно исключений. Объявление функции (включающее ее спецификацию исключений) ничего не сообщает относительно того, является ли она корректной, переносима, эффективной, какие гарантии безопасности исключений она предоставляет и предоставляет ли их вообще. Все эти характеристики определяются реализацией функции, а не ее объявлением.
Безопасный относительно исключений код должен представлять одну из трех описанных гарантий. Если он этого не делает, он не является безопасным. Выбор, таким образом, в том, чтобы определить, какой тип гарантии должна представлять каждая из написанных вами функций. Если не считать унаследованный код, небезопасный относительно исключений (об этом мы поговорим далее в настоящем правиле), то отсутствие гарантий допустимо лишь, если в результате анализа требований было решено, что приложение просто обязано допускать утечку ресурсов и работать с поврежденными структурами данных.
Вообще говоря, нужно стремиться предоставить максимально строгие гарантии. С точки зрения безопасности исключений функции, не возбуждающие исключений, чудесны, но очень трудно, не оставаясь в рамках языка C, обойтись без вызова функций, возбуждающих исключения. Любой класс, в котором используется динамическое распределение памяти (например, STL-контейнеры), может возбуждать исключение bad_alloc, когда не удается найти достаточного объема свободной памяти (см. правило 49). Предоставляйте гарантии отсутствия исключений, когда можете, но для большинства функций есть только выбор между базовой и строгой гарантией.
Для функции changeBackground предоставить почти строгую гарантию нетрудно. Во-первых, измените тип данных bgImage в классе PrettyMenu со встроенного указателя *Image на один из «интеллектуальных» управляющих ресурсами указателей, описанных в правиле 13. Откровенно говоря, это в любом случае неплохо, поскольку позволяет избежать утечек ресурсов. Тот факт, что это заодно помогает обеспечить строгую гарантию безопасности исключений, просто подтверждает приведенные в правиле 13 аргументы в пользу применения объектов (наподобие интеллектуальных указателей) для управления ресурсами. Ниже я воспользовался классом tr1::shared_ptr, потому что он ведет себя более естественно при копировании, чем auto_ptr.
Во-вторых, нужно изменить порядок предложений в функции changeBackground так, чтобы значение счетчика imageChanges не увеличивалось до тех пор, пока картинка не будет заменена. Общее правило таково: помечайте в объекте, что произошло некоторое изменение, только после того, как это изменение действительно выполнено.
Вот что получается в результате:
class PrettyMenu {
...
std::tr1::shared_ptr bgImage;
...
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock ml(mutex);
BgImage.reset(new Image(imgSrc)); // заменить внутренний указатель
// bgImage результатом выражения
// “new Image”
++imageChanges;
}
Отметим, что больше нет необходимости вручную удалять старую картинку, потому что это делает «интеллектуальный» указатель. Более того, удаление происходит только в том случае, если новая картинка успешно создана. Точнее говоря, функция tr1::shared_ptr::reset будет вызвана, только в том случае, когда ее параметр (результат вычисления «new Image(imgSrc)») успешно создан. Оператор delete используется только внутри вызова reset, поэтому если функция не получает управления, то и delete не вызывается. Отметим также, что использование объекта (tr1::shared_ptr) для управления ресурсом (динамически выделенным объектом Image) ко всему прочему уменьшает размер функции changeBackground.
Читать дальше
Конец ознакомительного отрывка
Купить книгу