Анимация
JavaScript
|
Главная Библионтека STATic CHECKCsizeofCFroni) <= sizeofCTo)); return reinterpret cast<To>Cfrom); void* somepointer = ...; char с = safe reinterpret cast<char>CsomePointer); Если размер системного указателя превышает размер символа, компилятор выдаст сообщение, что профамма пытается создать массив нулевой длины. С этим подходом связана одна проблема - сообщение об ошибке, выдаваемое компилятором, малоинформативно. "Невозможно создать массив нулевой длины" совсем не означает, что "Тип char слишком узок для указателя". Сделать сообщения об ошибках настраиваемыми и платформно-независимыми очень трудно. Для сообщений об ошибках не существует правил. Все они характерны лишь для данного компилятора. Например, если ошибка относится к неопределенной переменной, имя этой переменной в сообщении может не указываться. Гораздо лучше положиться на шаблоны, имеюшие информативные имена. К счастью, компилятор обязан указать имя такого шаблона в сообщении об ошибке. tempiate<boo1> struct CompileTimeError; tempiateo struct Compi1eTimeError<true> {}; #define STATlC CHECKCexpr) \ CCompi1eTimeError<Cexpr) != 0>C)) Структура Compi leTimeError является шаблоном, получающим параметр, не являющийся типом (булевскую константу). Она определена только для значения true этой булевской константы. Если попытаться конкретизировать шаблон с помощью выражения Compi1eTimeError<fa1se>, компилятор выдаст примерно такое сообщение: "Неопределенная спещ1ализаиия Compi 1eTimeError<fa1se>". Это сообщение немного содержательнее предыдущего и говорит о том, что ошибка сделана преднамеренно. Разумеется, здесь есть простор для совершенствования. Можно ли настраивать сообщения об ошибках? Идея состоит в следующем. Нужно передать дополнительный параметр в шаблон STATIC CHECK и каким-то образом сделать так, чтобы он появился в сообщении об ошибке. К недостаткам этого приема следует отнести тот факт, что настраиваемое сообщение об ошибке должно подчиняться правилам образования идентификаторов в языке С++ (не содержать пробелов, не начинаться с цифры и т.д.). Это приводит к улучшенной версии шаблона Compi leTimeError, показанной ниже. Фактически шаблон Compi leTimeError в новом контексте становится малопонятным. Более осмысленным является шаблон CompileTimeChecker. tempiate<boo1> struct CompileTimeChecker; { CompileTimeCheckerС...); templateo struct Compi1eTimeChecker<fa1se> {}; #define STATlC CHECKCexpr, msg) \ {\ class ERROR ##msg {}; \ Cvoid)sizeofCCompi1eTimeChecker<Cexpr) != 0> CCERROR ##msgC))));\ Предположим, что sizeofCchar) < sizeof(void*). (Стандарт языка не гарантирует, что это выражение обязательно является истинным.) Посмотрим, что произойдет, если написать следующий фрагмент профаммы. template <class то, class from> To safe reinterpret cast(From from) STATlC CHECK(si2eof(From) <= sizeofCro), Destination Type Too Narrow); return reinterpret cast<To>(from); void* somePointer = ...; char с = safe reinterpret cast<char>(somePointer); После обработки макроса препроцессором код функции safe reinterpret cast примет следующий вид. template <class то, class from> То safe reinterpret cast(From from) class ERROR Destination Type Too Narrow {}; (void)sizeof( CompileTimeChecl<er<(sizeof(From)) <= sizeof(To))>( ERROR Destination Type Too Narrow ())); return reinterpret cast<To>(from); Этот код определяет лоА:а/7ь«ын класс (local class) ERROR Destination Type Too Narrow, имеющий пустое тело. Затем он создает временное значение типа Compi 1 eTi meChecl<er<si zeof (From) <= sizeof(To))>, инициализированное временным значением типа ERROR Destination Type Too Narrow. В заключение функция si zeof вычисляет размер возникшей временной переменной. А теперь сделаем фокус! Специализация Compi 1 eTimeChecker<true> содержит конструктор, не имеющий аргументов. Это значит, что если выражение, проверяемое на этапе компиляции, имеет значение true, то полученная в результате профамма считается правильной, в противном случае возникает ошибка компиляции. Компилятор не может выполнить преобразование из типа ERROR Destination Type Too Narrow в тип Compi leTimeChecker<false>. Приятнее всего, что теперь компилятор выводит приблизительно такое сообщение об ошибке: "Ошибка; невозможно преобразовать тип ERROR Destination Type Too Narrow в тип compileTimeChecker<false>". Есть! 2.2. Частичная специализация шаблонов Частичная специализация шаблонов позволяет специализировать шаблонный класс с помощью подмножества его возможных парамефов конкретизации. Рассмотрим шаблонный класс Widget. template < class window, class Controller> class widget .. обобщенная реализация . . . Применим полную явную специализацию шаблона, template о class widget <ModalDialog, MyController> { .. специализированная реализация ... Классы ModalDialog и MyController определяются в прикладной программе. Обнаружив определение специализации класса widget, при определении объектов типа widget<ModalDialog, MyControner> компилятор будет применять специализированную, а в остальных случаях - обобщенную реализацию. Однако иногда возникает необходимость специализировать класс widget для любых конкретизации класса Window и отдельной конкретизации класса MyController. Вот как выглядит частичная специализация щаблона в этом случае. Частичная специализация шаблонного класса widget template <class window> ... частичная специализированная реализация ... Обычно в частичной специализации шаблонного класса указываются лишь некоторые шаблонные аргументы, а остальные остаются обобщенными (generic). Конкретизируя шаблонный класс в программе, компилятор пытается найти оптимальное соответствие. Сложный и точный алгоритм поиска соответствий позволяет программисту каждый раз по-новому осуществлять частичную специализацию. Допустим, что шаблонный класс Button получает один шаблонный параметр. Затем, даже если класс widget специализируется для любых конкретизации класса window и отдельной конкретизации класса MyController, класс widget можно и дальше частично специализировать для всел: конкретизации класса Button и конкретизации MyController. template <class ButtonArg> class widget<Button<ButtonArg>, MyController> { Как видим, возможности частичной шаблонной специализации поразительны. Конкретизируя шаблон, компилятор выполняет сопоставление с эталоном (pattern matching) существующих частичных и полных специализаций, подыскивая наилу1ший вариант. Это обеспечивает программисту невероятную свободу действий. К сожалению, частичную шаблонную специализацию невозможно применить к функциям, независимо от того, являются они членами класса или нет. Это несколько снижает уровень гибкости и степень детализации программы. • Несмотря на то что в шаблонном классе можно выполнять полную шаблонную специализацию функций-членов, к ним нельзя применять частичную шаблонную специализацию. • Шаблонные функции из просфанства имен (не являющиеся членами какого-либо класса) невозможно Частично специализировать. Больше всего на частичную специализацию шаблонных функций из пространства имен (namespace-level template functions) похож процесс их замещения. С практической точки зрения это означает, что возможность детальной специализации существует лишь для параметров функций, но не для возвращаемых ими значений или используемых внутри нее типов. template <class т, class и> т Fun(u obj); исходный шаблон template <class и> void Fun<void, и>(и obj); неправильная частичная специализация template <class т> т Fun (window obj); правильно (перегрузка) 0 1 2 3 4 5 6 7 8 9 10 [ 11 ] 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |