|
Программирование >> Разработка устойчивых систем
Статья Curiously Recurring Template Patterns* в сборнике С++ Gems под редакцией Стена Лип-мана (Stan Lippman), SIGS, 1996 г. cout CountedClass::getCount() endl: 2 CountedClass2 c: cout CountedClass2::getCount() endl: 3 (Ошибка) } III:- Bee классы, производные от Counted, используют одну статическую переменную, поэтому количество объектов отслеживается сразу по всем классам иерархии Counted. Нам же нужен способ автоматического построения отдельного базового класса для каждого производного класса. Задача решается при помоши занятной шаблонной конструкции, представленной ниже: : C05:CountedClass3.cpp linclude <iostream> using namespace std: tempiate<class T> class Counted { static int count: public: CountedО { ++count: } Counted(const Counted<T>&) { ++count: } -CountedO { --count: } static int getCountO { return count: } tempiate<class T> int Counted<T>::count = 0: Необычные объявления классов class CountedClass : public Counted<CountedClass> {}; class CountedClass2 : public Counted<CountedClass2> {}; int mainO { CountedClass a; cout CountedClass::getCount() endl: 1 CountedClass b: cout CountedClass::getCount() endl: 2 CountedClass2 c: cout CountedClass2::getCount() endl: 1 (!) } III:- Каждый производный класс наследует от уникального базового класса, при определении которого в параметре шаблона указывается сам производный класс! На первый взгляд похоже на циклическое определение, и так бы оно и было, если бы какие-нибудь члены базовых классов использовали аргумент шаблона в вычислениях. Но так как ни одна переменная Counted не зависит от Т, размер Counted (ноль!) известен на момент обработки шаблона. А значит, какой бы аргумент ни использовался для специализации Counted, размер все равно не изменится. Любое наследование от специализации Counted полностью определяется на момент обработки, и никакой рекурсии не возникает. Поскольку каждый базовый класс уникален, он обладает собственным набором статических данных, поэтому в вашем распоряжении оказывается удобная методика для организации подсчета объектов в абсолютно любом классе. Первое упоминание об этой любопытной идиоме встречается в статье Джима Коплин (Jim Coplien). С технической точки зрения речь идет о константах времени компиляции, и по стандартным правилам их следовало бы записать прописными буквами. Мы оставили для имен строчные буквы, потому что они имитируют переменные. Шаблонное метапрограммирование в 1993 году в компиляторах постепенно стала реализовываться поддержка простых шаблонных конструкций, а пользователи начали определять обобщенные контейнеры и функции. Примерно в то же время, когда рассматривалась возможность включения STL в стандарт С++, члены комитета по стандартизации С++ обменивались друг с другом интересными и удивительными примерами вроде следующего*: : С05:Factorial.срр Вычисление факториала на стадии компиляции! linclude <iostream> using namespace std; tempiate<int n> struct Factorial { enum {val = Factorial<n-l>:;val * n}; templateo struct Factorial<0> { enum {val = 1}; int mainO { cout Factorial<12>::val endl; 479001600 } III:- Программа выводит правильное значение 12!, и это нормально. Удивляет другое - результат вычисляется еще до запуска программы! Пытаясь создать специализацию Factorial<12>, компилятор выясняет, что он также должен создать специализацию Factorial<ll>, для которой нужна специализация Factorial<10> и т. д. Рекурсия заканчивается на специализации Factorial<l>, после чего вычисления проводятся в обратном направлении. В конечном счете переменная Factorial<12>::val замещается целочисленной константой 479 001 600, и компиляция завершается. Поскольку все вычисления выполняются компилятором, участвующие в них значения должны быть константами времени компиляции, отсюда и использование константы enum. При запуске программы остается лишь вывести эту константу и символ перевода строки. Чтобы убедиться в том, что специализация Factorial создает правильное значение времени компиляции, вы можете указать его как размерность массива: double nums[Factorial<5>;:val]; assertCsizeof nums == sizeof(double)*120); Программирование на стадии компиляции Итак, то, что задумывалось как удобный способ подстановки типовых параметров, превратилось в некое подобие механизма программирования на стадии компиляции. Такие программы, которые называются шаблонными метапрограммами, оказываются способными на многое. Шаблонное метапрограммирование обладает полнотой по Тьюрингу, поскольку оно поддерживает выбор (if-else) и циклы (посредством рекурсии). Следовательно, теоретически в метапрограммах можно выполнять любые вы- Ha языке математики числа Фибоначчи определяются так: 0, W = о, 1, W = 1, Л-2+Л-2. >1- Первые два правила оформляются как специализации, а третье правило преобразуется в основной шаблон. Циклы на стадии компиляции Чтобы выполнить любой цикл в шаблонной метапрограмме, необходимо сначала сформулировать его в рекурсивном виде. Например, для возведения целого числа п в степень р в обычной программе применяется цикл вида: int val = 1: while(p--) val *- п: Вместо этого цикла можно воспользоваться рекурсивной процедурой: int power(int п. int р) { return (р 0) ? 1 : n*power(n.p - 1): Она легко формулируется в виде шаблонной метапрограммы: : COS:Power.срр #i nclude <iostream> using namespace std; tempiate<int N. int P> struct Power { enum {val - N * Power<N, P-l>::val}: В 1966 году Бём (Bohm) и Якопини (Jacopini) доказали, что любой язык с поддержкой конструкций условного выбора и циклов, позволяющий использовать произвольное количество переменных, эквивалентен машине Тьюринга, которая способна представить любой алгоритм. числения. Ранее приведенный пример с факториалом демонстрирует реализацию циклов: вы пишете рекурсивный шаблон и определяете критерий остановки посредством специализации. Следующий пример показывает, как аналогичным способом на стадии компиляции вычисляется последовательность чисел Фибоначчи: : С05:Fibonacci.срр #i nclude <iostream> using namespace std: tempiate<int n> struct Fib { enum {val = Fib<n-1>::val + Fib<n-2>::val}: templateo struct Fib<l> { enum {val =1}: }: templateo struct Fib<0> { enum {val =0}: }: int mainO { cout Fib<5>::val endl: 6 cout Fib<20>::val endl: 6765 } III:-
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |