Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
Шрифт:
Рассмотрим следующую возможную реализацию функции-члена change-Background:
С точки зрения безопасности исключений, эта функция настолько плоха, насколько вообще возможно. К безопасности исключений предъявляется два требования, и она не удовлетворяет ни одному из них.
Когда возбуждается исключение, то безопасная относительно исключений функция:
• Не допускает утечки ресурсов. Приведенный код не проходит этот тест, потому что если выражение «new Image(imgSrc)» возбудит исключение, то вызов unlock никогда не выполнится, и мьютекс окажется захваченным навсегда.
• Не допускает повреждения структур данных. Если «new Image(imgSrc)» возбудит исключение, в bgImage останется указатель на удаленный объект. Кроме того, счетчик imageChanges увеличивается, несмотря на то что новая картинка не установлена. (С другой стороны, старая картинка уже полностью удалена, так что трудно сделать вид, будто ничего не изменилось.)
Справиться с утечкой ресурсов легко – в правиле 13 объяснено, как пользоваться объектами, управляющими ресурсами, а в правиле 14 представлен класс Lock, гарантирующий своеременное освобождение мьютексов:
Одним из преимуществ классов для управления ресурсами, подобных Lock, является то, что обычно они уменьшают размер функций. Заметили, что вызов unlock уже не нужен? Общее правило гласит: чем меньше кода, тем лучше, потому что меньше возможностей для ошибок и меньше путаницы при внесении изменений.
От утечки ресурсов перейдем к проблеме возможного повреждения данных. Здесь у нас есть выбор, но прежде чем его сделать, нужно уточнить терминологию.
Безопасные относительно исключений функции предоставляют одну из трех гарантий.
• Функции, предоставляющие базовую гарантию, обещают, что если исключение будет возбуждено, то все в программе остается в корректном состоянии. Никакие объекты или структуры данных не повреждены, и все объекты находятся в непротиворечивом состоянии (например, все инварианты классов не нарушены). Однако точное состояние программы может быть непредсказуемо. Например, мы можем написать функцию change-Background так, что при возникновении исключения объект PrettyMenu сохранит старую фоновую картинку либо у него будет какой-то фон по умолчанию, но пользователи не могут заранее знать, какой. (Чтобы выяснить это, им придется вызвать какую-то функцию-член, которая сообщит, какая сейчас используется картинка.)
• Функции, предоставляющие строгую гарантию, обещают, что если исключение будет возбуждено, то состояние программы не изменится. Вызов такой функции является атомарным; если он завершился успешно, то все запланированные действия выполнены до конца, если же нет, то программа останется в таком состоянии, как будто функция никогда не вызывалась.
Работать с функциями, представляющими такую гарантию, проще, чем с функциями, которые дают только базовую гарантию, потому что после их вызова может быть только два состояния программы: то, которое ожидается в результате ее успешного завершения, и то, которое было до ее вызова. Напротив, если исключение возникает в функции, представляющей только базовую гарантию, то программа может оказаться в любом корректном состоянии.
• Функции, предоставляющие гарантию отсутствия исключений, обещают никогда не возбуждать исключений, потому что всегда делают то, что должны делать. Все операции над встроенными типами (например, целыми, указателями и т. п.) обеспечивают такую гарантию. Это основной строительный блок безопасного относительно исключений кода. Разумно предположить, что функции с пустой спецификацией исключений не возбуждают их, но это не всегда так. Например, рассмотрим следующую функцию:
Это объявление не говорит о том, что doSomething никогда не возбуждает исключений. Утверждается лишь, что если doSomething возбудит исключение, значит, произошла серьезная ошибка и должна быть вызвана функция unexpected [3] . Фактически doSomething может вообще не представлять никаких гарантий относительно исключений. Объявление функции (включающее ее спецификацию исключений) ничего не сообщает относительно того, является ли она корректной, переносима, эффективной, какие гарантии безопасности исключений она предоставляет и предоставляет ли их вообще. Все эти характеристики определяются реализацией функции, а не ее объявлением.
3
Более подробную информацию о функции unexpected вы можете найти, воспользовавшись своим любимым поисковым сервисом или в полном руководстве по языку C++ (возможно, стоит поискать информацию о функции set_unexpected, которая специфицирует unexpected).
Безопасный относительно исключений код должен представлять одну из трех описанных гарантий. Если он этого не делает, он не является безопасным. Выбор, таким образом, в том, чтобы определить, какой тип гарантии должна представлять каждая из написанных вами функций. Если не считать унаследованный код, небезопасный относительно исключений (об этом мы поговорим далее в настоящем правиле), то отсутствие гарантий допустимо лишь, если в результате анализа требований было решено, что приложение просто обязано допускать утечку ресурсов и работать с поврежденными структурами данных.