51. Деструкторы, функции освобождения ресурсов и обмена не ошибаются

We use cookies. Read the Privacy and Cookie Policy

51. Деструкторы, функции освобождения ресурсов и обмена не ошибаются

Резюме

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

Обсуждение

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

Рассмотрим следующие советы и требования, найденные в Стандарте С++.

Если деструктор, вызванный в процессе свертки стека, выходит с исключением, вызывается функция terminate (15.5.1). Поэтому деструкторы должны в общем случае перехватывать исключения и не позволять им распространиться за пределы деструктора.

[C++03] §15.2(3)

В стандартной библиотеке C++ не определен ни один деструктор [включая деструкторы любого типа, используемого для инстанцирования шаблона стандартной библиотеки], генерирующий исключение.

— [C++03] §17.4.4.8(3)

Деструкторы являются специальными функциями, и компилятор автоматически вызывает их в различных контекстах. Если вы пишете класс — назовем его, к примеру, Nefarious[2] — деструктор которого может давать сбой (обычно посредством генерации исключения; см. рекомендацию 72), то вы столкнетесь с такими последствиями.

• Объекты Nefarious трудно безопасно использовать в обычных функциях. Вы не можете надежно инстанцировать автоматические объекты Nefarious в области видимости, если возможен выход из этой области видимости посредством исключения. Если это произойдет, деструктор Nefarious (вызываемый автоматически) может попытаться сгенерировать исключение, которое приведет к неожиданному завершению всей программы посредством вызова Терминатора — std::terminate (см. также рекомендацию 75).

• Трудно безопасно использовать классы с членами или базовыми классами Nefarious. Плохое поведение класса Nefarious распространяется на все классы, для которых он является базовым или у которых имеются члены этого типа.

• Вы не можете надежно создавать глобальные либо статические объекты Nefarious. Исключение, которое может быть сгенерировано их деструкторами, невозможно перехватить.

• Вы не можете надежно создавать массивы объектов Nefarious. Коротко говоря, массивы при наличии деструкторов, которые могут генерировать исключения, обладают неопределенным поведением, поскольку в этой ситуации просто невозможно придумать способ разумного отката. (Подумайте сами: какой именно код должен сгенерировать компилятор для создания массива из десяти объектов Nefarious, если у четвертого объекта в конструкторе происходит генерация исключения, а при откате, когда вызываются деструкторы уже сконструированных объектов, один или несколько деструкторов генерируют исключения? Удовлетворительного ответа в этой ситуации просто нет.)

• Вы не можете использовать объекты Nefarious в стандартных контейнерах. Объекты Nefarious нельзя хранить в стандартных контейнерах или использовать их с какими-то другими частями стандартной библиотеки. Стандартная библиотека запрещает использование деструкторов, которые могут генерировать исключения.

Функции освобождения ресурсов, включая специальным образом перегруженные операторы operator delete и operator delete[], попадают в ту же категорию, поскольку в общем случае они также используются в процессе "зачистки", в частности, в процессе обработки исключений.

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

T& T::operator=(const T& other) {

 T temp(other);

 Swap(temp);

}

(см. также рекомендацию 56)

К счастью, область видимости сбоя при освобождении ресурса имеет определенно меньший размер. Если для сообщения об ошибке используются исключения, убедитесь, что рассматриваемые функции обрабатывают все возможные исключения и прочие ошибки, которые могут возникнуть при работе функций. (В случае исключений просто оберните все, что делает ваш деструктор, в блок try/catch(...).) Это чрезвычайно важно, поскольку деструктор может быть вызван в кризисной ситуации, такой как сбой при выделении системного ресурса (например, памяти, файлов, блокировок, портов, окон или других системных объектов).

При использовании в качестве механизма обработки ошибок исключений следует документировать такое поведение, объявляя такие функции с закомментированной пустой спецификацией исключений /* throw() */ (см. рекомендацию 75).

Ссылки

[C++03] §15.2(3), §17.4.4.8(3) • [Meyers96] §11 • [Stroustrup00] §14.4.7, §E.2-4 • [Sutter00] §8, §16 • [Sutter02] §18-19