KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация

Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Герб Саттер, "Стандарты программирования на С++. 101 правило и рекомендация" бесплатно, без регистрации.
Перейти на страницу:

Вывод: всегда явно пишите деструктор базового класса, поскольку неявно сгенерированный деструктор является открытым и невиртуальным.

Примеры

Клиентский код должен либо быть способен полиморфно удалять объекты посредством указателя на базовый класс, либо не должен этого делать. Каждый выбор определяет свой дизайн.

• Пример 1. Базовые классы с полиморфным удалением. Если должно быть разрешено полиморфное удаление, то деструктор должен быть открытым (в противном случае вызывающий код не сможет к нему обратиться) и должен быть виртуальным (в противном случае его вызов приведет к неопределенному поведению).

• Пример 2. Базовые классы без полиморфного удаления. Если полиморфное удаление не должно быть разрешено, деструктор должен не быть открытым (чтобы вызывающий код не мог к нему обратиться) и не должен быть виртуальным (потому что виртуальность ему не нужна).

Классы стратегий часто используются в качестве базовых для удобства, а не для обеспечения полиморфного поведения. Их деструкторы рекомендуется делать защищенными и невиртуальными.

Исключения

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

Рассмотрим также еще один редкий случай.

• В одновременно является как базовым классом, так и конкретным классом, для которого могут создаваться объекты (так что деструктор этого класса должен быть открытым, чтобы было можно создавать и уничтожать объекты данного типа).

• У В нет виртуальных функций, и он не предназначается для полиморфного использования (так что хотя деструктор и открыт, он не обязан быть виртуальным).

Тогда несмотря на то, что деструктор оказывается открытым, имеется огромный соблазн не делать его виртуальным, поскольку такая первая виртуальная функция может привести к излишним расходам времени выполнения из-за добавления функциональности, которая нам совсем не нужна.

В этом редком случае вы можете сделать конструктор открытым и невиртуальным, но четко документировать, что производные объекты не могут использоваться полиморфно в качестве объектов базового класса. Именно это было сделано в случае класса std::unary_function.

В общем случае, однако, следует избегать конкретных базовых классов (см. рекомендацию 35). Например, unary_function представляет собой набор определений typedef, который не предназначен для создания объектов данного типа. В действительности бессмысленно давать ему открытый деструктор; лучше было бы последовать совету данной рекомендации и сделать его деструктор защищенным и невиртуальным.

Ссылки

[Cargill92] pp. 77-79, 207 • [Cline99] §21.06, 21.12-13 • [Henricson97] pp. 110-114 • [Koenig97] Chapters 4,11 • [Meyers97] §14 • [Stroustrup00] §12.4.2 • [Sutter02] §27 • [Sutter04] § 18

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).

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*