|
Программирование >> Разработка устойчивых систем
template<bool cond> void executeO { Select<cond>::f(): int mainO { execute<sizeof(int) - 4>(): } III:- Эта программа эквивалентна следующему выражению: if(cond) statementlO: else statement2(): Отличие только в том, что условие cond проверяется на стадии компиляции, а компилятор создает специализации соответствующих версий executeo() и Selecto. Функция Selecto::fО выполняется на стадии выполнения. Команда switch эмулируется аналогичным образом, только специализация производится по всем вариантам вместо двух (true и false). Утверждения времени компиляции в главе 2 отмечалась польза утверждений как части общей стратегии защитного программирования. Утверждение фактически представляет собой проверку логического выражения, за которой программа либо не делает ничего (если условие истинно), либо аварийно завершается с выдачей диагностических сообщений. Понятно, что нарушения утверждений должны выявляться как можно раньше. Если условие может быть проверено на стадии компиляции, лучше использовать утверждение времени компиляции. В следующем примере показан простой прием с отображением логического выражения на объявление массива: : С05:StaticAssertl.срр {-хо} Простой механизм проверки условий времени компиляции fdefine STATIC ASSERT(x) \ do { typedef int a[(x) ? 1 : -1]: } while (0) int mainO { STATIC ASSERT(sizeof(int) <- sizeofdong)); Проходит STATIC ASSERT(sizeof(double) <- sizeof(int)): He проходит } III:- Цикл do создает временную область видимости для определения массива, размер которого задается проверяемым условием. Определение массива с размером -1 недопустимо, поэтому при нарушении условия команда также не выполняется. В предыдущем разделе было показано, как организовать проверку логических выражений на стадии компиляции. Однако для полноценной эмуляции проверки утверждений на стадии компиляции нужно решить еще одну задачу: вывести осмысленное сообщение об ошибке и завершить работу программы. Для прерывания работы компилятора достаточно простой ошибки компиляции; фокус заключается в том, чтобы вставить осмысленный текст в сообщение об ошибке. В следующем примере, предложенном Александреску (Alexandrescu), используется специализация шаблона, локальный класс и трюк с макросами: : C05:StaticAssert.cpp {-g++} linclude <iostream> using namespace std: Шаблон и специализация tempiate<bool> struct StaticCheck { StaticCheck(...): templateo struct StaticCheck<false>{}: Макрос (генерирует локальный класс) Idefine STATIC CHECK(expr. msg) { \ class Error msg{}; \ sizeof((StaticCheck<expr>(Error msg()))): \ Обнаружение сужающих преобразований template<class To. class From> To safe cast(From from) { STATIC CHECK(SizeofCFrom) <- sizeoflTo). NarrowingConversion): return reinterpret cast<To>(from): int mainO { void* p - 0; int i - safe cast<int>(p): cout int cast okay endl: ! char с - safe cast<char>(p): } III:- B этом примере определяется шаблон функции safe cast<>() для обнаружения сужающих преобразований, когда тип исходного объекта больше целевого типа, получаемого в результате преобразования. Если тип целевого объекта оказывается меньше, пользователь во время компиляции получает сообщение о попытке сужающего преобразования. Обратите внимание на любопытную особенность шаблона StaticCheck: к специализации StaticCheck<true> можно преобразовать все, что угодно (благодаря многоточию в конструкторе), а к специализации StaticCheck<false> нельзя преобразовать ничего, потому что для этой специализации отсутствуют преобразования. Идея заключается в том, чтобы попытаться создать экземпляр нового класса и преобразовать его в объект StaticCheck<true> во время компилящи, если проверяемое условие истинно, или в объект StaticCheck<false>, если условие ложно. Поскольку оператор sizeof выполняет свою работу на стадии компиляции, он задействуется для попытки выполнения преобразования. Если условие ложно, компилятор сообщает, что он не знает, как преобразовать тип нового класса к StaticCheck<false> (дополнительные круглые скобки внутри вызова sizeof в $ТАПС СНЕСК() нужны для того, чтобы компилятор не решил, будто мы пытаемся вызвать оператор sizeof для функции, что недопустимо). Чтобы сообщение об ошибке содержало полезную информацию, ключевой текст помещается в имя нового класса. Впрочем, эту методику проще всего разобрать на конкретном примере. Возьмем следующую строку в функции main(): int i - safe cast<int>(p): В вызове safe cast<int>(p) первая строка программы заменяется следующим расширением макроса (вспомните, что препроцессорный оператор ## выполняет Стоит заметить, что помимо сохранения математической записи и оптимизации кода шаблоны выражений позволяют реализовывать в библиотеках С++ парадигмы и механизмы (такие как лямбда-выражения) других языков программирования. В качестве еще одного примера можно назвать фантастическую библиотеку классов Spirit (за информацией обращайтесь на сайт http: spirit.sourceforge.net/). конкатенацию своих операндов, поэтому Error ##NarrowingConversion после обработки препроцессором превращается в Error Narrowing conversion): class Error NarrowingConversion{}: \ sizeof(StaticCheck<sizeof(voict*) <= sizeof(int)> \ (Error NarrowingConversion())): \ Класс Error Narrowing conversion объявляется кгт локальный, поскольку в программе он больше нигде не используется. Оператор sizeof пытается определить размер экземпляра StaticChecl true> (так как условие sizeof(void*)<=sizeof(int) истинно на всех платформах), неявно созданного на базе временного объекта, возвращаемого вызовом Error NarrowingConversion(). Компилятор знает размер нового 1сласса Error Narrowing conversion (так как он пуст), поэтому использование sizeof во время компиляции на внешнем уровне STATIC CHECK() допустимо. Преобразование временного объекта Error Narrowing conversion в StaticChecl true> проходит успешно, внешнее применение StaticChecl true> тоже, и выполнение профаммы продолжается. Теперь посмотрим, чтоб произойдет, если раскомментировать последнюю строку main(): char с = safe cast<char>(p): На этот раз макрос STATIC CHECK() внутри safe cast<char>(p) расширяется в следующий фрагмент: class Error Narrow1ngConversion{}: \ sizeof(Stat1cCheck<sizeof(void*) <= s1zeof(char)> \ (Error Narrowi ngConversIon())): \ Поскольку выражение sizeof(void*)<=sizeof(char) ложно, компилятор пытается преобразовать временный объект Error NarrowingConversion в StaticChecl false>: s1zeof(Stat1cCheck<false>(Error NarrowingConversion())): Попытка завершается неудачей, и компилятор прекращает свою работу с выдачей сообщения вида Cannot cast from Error NarrowingConvers1on to Stat1cCheck<0> in function char safe cast<char.void *>(void *) Имя класса Error NarrowingConversion представляет собой осмысленное сообщение, специально подготовленное программистом. В общем случае для проверки статических утверждений достаточно вызвать макрос STATIC CHECK с проверяемым условием и осмысленным именем, описывающим ошибку. Шаблоны выражений Вероятно, самое мощное применение шаблонам было найдено в 1994 г. Тоддом Вельдхузеном (Todd Veldhuizen) и Дэвидом Вандерворде (Daveed Vandervoorde) независимо друг от друга. Шаблоны выражений позволяют кардинально оптими-
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |