|
Программирование >> Полиморфизм без виртуальных функций в с++
обработка исключений catch (el) { throw; возбудить повторно catch (е2) { throw; возбудить повторно catch (...) { unexpectedO ; Преимущество явного объявления исключений по сравнению с эквивалентной проверкой в тексте не только в том, что нужно набирать меньше символов. Важнее то, что объявление функции является частью интерфейса, видимого пользователю. Определение же функции доступно не всем, и, даже если имеются исходные тексты всех библиотек, лучше бы заглядывать в них пореже. Еще одно преимущество состоит в том, что на практике может оказаться полезным обнаруживать неперехваченные исключения еще на стадии компиляции [Koenig, 1990]. Спецификации исключений следовало бы проверять на стадии компиляции, но для этого все функции должны были бы вести себя согласованно, а это вряд ли возможно. К тому же такая статическая проверка легко могла бы послужить причиной многократной повтор1юй ко.мпиляции профаммы. Более того, переко.мпилировать профамму могли бы лишь пользователи, обладающие всеми исходными текстами: Например, потенциально функцию необходимо изменить и перекомпилировать всякий раз, как в функции, которую она вызывает (прямо или косвенно), изменяется множество перехваты ваемых или возбуждаемых исключений. Это может привести к серьезным срывам в сроках по ставки программы, составленной (пусть даже частично) из нескольких независимо разработанных библиотек. В таких библиотеках де-факто должен был бы согласовываться набор используемых исключений. Например, если подсистема X обрабатывает исключения, возбуждаемые в подсистеме У, а поставщик У вводит новый тип исключения, то код X пришлось бы модифицировать. Пользователь и X, и У не сможет поставить новую версию своего продукта, пока обе подсистемы не будут модифицированы. В случаях когда используется много подсистем, это может привести к целой цепочке задержек. Но даже и там, где пользователь прибегает к услугам только одного поставщика, это может стать причиной каскадной модификации кода и многократных перекомпиляций. Из-за такого рода проблем многие стараются вовсе не прибегать к механизму спецификации исключений или как-то обойти его [Koenig, 1990]. Поэтому мы решили поддержать только проверку во время исполнения, а статический контроль оставить специальным инструментальным средствам. Аналогичная трудность возникает также при использовании динамической проверки. Однако в этом случае ее можно решить с помощью механизма группировки исключений, описанного в разделе 16.4. При наивном подходе новое исключение, добавленное к подсистеме У, осталось бы неперехваченным или было бы преобразовано в вызов функции unexpected () некоторым явно вызываемым интерфейсом. Однако если подсистема Y построена правильно, то все ее исключения были бы производными от класса Yexcept ion. Например: class newYexception : public Yexception ( /* ... */ }; При этом функция, объявленная как void f() throw (Xexception, Yexception, lOexception); смогла бы обработать иcключeниenewYexception, передав его функции, вызвавшей f () . Более подробное обсуждение см. в [2nd, §9]. В 1995 г. была найдена схема, которая позволяет в какой-то .мере выполнить статический контроль спецификации исключений, не порождая описанных выше проблем. Поэтому теперь спецификации исключений проверяются, так что инициализация и присваивание указателю на функцию, а также замещение виртуальных функций не могут нарушить защиту. Некоторые неожиданные исключения все же возможны, и они, как и раньше, перехватываются во время исполнения. 16.9.1. Вопросы реализации Как обычно, эффективности уделялось самое пристальное внимание. Очевидно, что можно было спроектировать такой механизм обработки исключений, реализовать который удалось бы лишь за счет явных затрат при вызове функций или неявных, связанных с невозможностью некоторых видов оптимизации. Похоже, нам удалось этого избежать, поскольку, по крайней мере, теоретически механизм исключений в С++ можно реализовать без каких бы то ни было временных издержек в программах, которые не возбуждают исключений. Компилятор можно построить так, что все временные издержки будут проявляться лишь тогда, когда исключение действительно возбуждается [Koenig, 1990]. Ограничить расход памяти также не составляет особой сложности, но очень трудно одновременно избежать и временных, и пространственных затрат. Сейчас есть уже несколько ко.мпи-ляторов, поддерживающих исключения, поэтому последствия компромиссов можно сравнить (см., например, [Cameron, 1992]). Как ни странно, обработка исключений почти не отражается на модели размещения объекта в памяти. Для обеспечения взаимодействия между точкой возбуждения исключения и обработчиком иеобходи.мо уметь представлять тип во время выполнения. Однако это можно сделать с помощью специализированного механизма, не затрагивающего объект в целом, используя структуры данных, поддерживающие идентификацию типа во время исполнения (см. раздел 14.2.6). Гораздо важнее то, что особенно значимым становится отслеживание точного времени жизни всех автоматических объектов. Прямолинейная реализация может привести к неоправданному увеличению кода, несмотря даже на то, что число дополнительных команд, которые действительно выполняются, невелико. Техника реализации, представляющаяся оптимальной, заимствована из работ по языкам Clu и Modula-2-(- [Rovner, 1986]. Основная идея состоит в том, чтобы составить таблицу диапазонов адресов кода, соответствующих состоянию вычислений, относящихся к обработке исключений. Для каждого диапазона регистрируются деструкторы и обработчики исключений, которые следует вызвать. Когда возбуждается исключение, механизм его обработки сравнивает текущее значение счетчика команд с адресами в таблице диапазонов. Если оно попадает в один из диапазонов, то предпринимаются соответствующие действия; в противном случае производится раскрутка стека и в таблице ищется значение счетчика команд для вызвавшей функции. 16.10. Инварианты Поскольку язык С++ еще сравнительно молод, продолжает развиваться и в то же время широко используется, постоянно появляются предложения об улучшениях и расширениях. Как только в каком-нибудь языке появлялось новомодное средство, его рано или поздно начинали предлагать включить в С++. Бертран Мейер (Bertrand Meyer) популяризировал старую идею о пред- и постусловиях и ввел для нее прямую поддержку в языке Eiffel [Meyer, 1988]. Естественно, то же самое предлагалось сделать и для С++. Определенная часть пользователей С всегда интенсивно работала с макросом assert О , но не было приемлемого способа сообщить о нарушении утверждения во время выполнения. Такой способ дают исключения, в то время как шаблоны позволяют отказаться от макросов. Например, вот как можно написать шаблон Assert (), который имитирует С-макрос assert (): template<class Т, class х> inline void Assert(Т expr,x x) { if (!NDEBUG) if (!expr) throw x; Oh будет возбуждать исключение x, если выражение ехрг ложно и мы не отключили проверку, задав ненулевое значение переменной NDEBUG. Например: class Bad f arg { }; void f(Strings s, int i) { Assert(0<=i && i<s.size(), Bad f arg()); ... Это наименее структурированный вариант такой техники. Я предпочитаю определять инварианты для классов в виде функций-членов, а не использовать утверждения напрямую. Например: void String::check() { Assert(p && 0<=sz && sz<TOO LARGE && p[sz-l]==0 , Invariant); Простота, с которой в уже существующем языке С++ можно определять и проверять утверждения и инварианты, намного уменьшила число тех, кто непременно желает включить расширение для прямой поддержки верификации программ. Поэтому большая часть работы, относящейся к подобным приемам, проходила по разряду предложений по методике стандартизации [Gautron, 1992], или была связана с более серьезными системами верификации [Lea, 1990], или просто велась в рамках имеющегося языка.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |