Анимация
JavaScript
|
Главная Библионтека клонирования изложены в главе 8.) Виртуальный деструктор позволяет уничтожать объекты классов, производных от класса Functorlmpl, применяя оператор delete к указателю на объект класса Functorlmpl. Важность этого деструктора подробно обсуждалась в главе 4. Классическая реализация класса Functor приведена ниже. template <typename R, class TList> class Functor public: FunctorC); FunctorCconst Functor&); Functor& operator=Cconst Functor&); explicit FunctorCstd::auto ptr<impl> splmpl); private: Определение тела функтора typedef Functorlmpl<R, TList> Imp!; std::auto ptr<lmpl> splmpl ; В классе Functor интеллектуальный указатель на класс Functorlmpl<R, TList>, представляющий собой соответствующий тип тела функтора, хранится в виде закрытого члена. Для этого выбран стандартный интеллектуальный указатель std: :auto ptr. Приведенный выще код иллюстрирует наличие у класса Functor определенных артефактов, демонстрирующих его семантику значений. К этим артефактам относятся конструктор по умолчанию, конструктор копирования и оператор присваивания. Явный деструктор не нужен, поскольку интеллектуальный указатель auto ptr автоматически освобождает все ресурсы. Кроме того, в классе Functor определен "конструктор расщирения", предостав-л5поший классу Functorlmpl доступ к указателю auto ptr. Конструктор расширения позволяет определять классы, производные от класса Functorlmpl, и непосредственно инициализировать класс Functor указателями на них. Почему аргументом конструктора расширения является итератор auto ptr, а не обычный указатель? Создание объекта с помощью указателя auto prt явно свидетельствует о том, что объект класса Functorlmpl принадлежит классу Functor. Вызывая этот конструктор, пользователь класса Functor должен явно указать тип auto ptr. Если он сделал это, значит, понимает, о чем идет речь."* * Разумеется, это вовсе не обязательно. Однако это все же лучше, чем молча выбирать один вариант (копирование или владение). Хорошие библиотеки на языке С++ отличаются одной интересной особенностью: когда может возникнуть неопределенность, они позволяют пользователю устранять ее с помощью явного кода. С другой стороны, существуют библиотеки, неправильно использующие свойства языка С+ + , определенные по умолчанию (особенно преобразования типов и владение указателями). Они предоставляют пользователям меньше возможностей для дополнительного программирования, но в качестве компенсации принимают сомнительные предположения и решения, облегчая работу пользователя. 5.5. Реализация оператора пересылки Functor: :operator() в классе Functor необходим оператор (), который пересылал бы вызов оператору Functorlmpl: :operator(). Для этого можно было бы воспользоваться подходом, который мы применяли при разработке самого класса Functorlmpl, и создать группу частичных шаблонных специализаций, каждая из которых соответствует конкретному количеству параметров. Однако этот подход здесь не годится. В классе Functor содержится довольно большое количество кода, и было бы неразумной тратой времени и памяти размножать его только для того, чтобы реализовать оператор (). Однако сначала попробуем определить типы параметров. Тут нам на помошь придут списки типов. template <typename R, class TList> class Functor typedef TList ParmList; typedef typename TypeAtNonStrict<TList, 0, EmptyType>:: Result Parml; typedef typename TypeAtNonStrict<TList, 1, EmptyType>:: Result Parm2; как и раньше .. Класс TypeAtNonStrict является шаблоном, обладающим доступом к типу по его позиции в списке типов. Если тип не найден, в качестве третьего шаблонного аргумента класса TypeAtNonStrict задается результат (т.е. внутренний класс TypeAtNon-Strict<. . .>: iResult). В качестве третьего аргумента выбран класс EmptyType, являющийся, как следует из его названия, пустым. (Подробное описание класса TypeAtNonStrict можно найти в главе 3, а описание класса EmptyType - в главе 2.) Итак, тип Рагтл/будет либо /V-м элементом списка типов, либо типом EmptyType, если количество элементов списка типов меньше, чем N. Чтобы реализовать оператор (), воспользуемся интересным трюком. Определим все версии оператора () - для любого количества параметров - внутри определения класса Functor. template <typename R, class TList> class Functor ... как и раньше ... public: R operatorOO return C*splmpl )(); operatorO (Parml pl) return (*spimpl )(pl); operatorO(Parml pl, Parm2 p2) return (*spimpl )(pl, p2); в чем состоит трюк? Для данной конкретизации класса Functor правильной является только одна версия оператора (). Все остальные версии на этапе компиляции порождают сообщения об ошибках. Можно предположить, что класс Functor не будет скомпилирован вовсе, поскольку в каждой специализации класса Functorlmpl определен только один оператор С), а не группа, как в классе Functor. Трюк заключается в том, что в языке С++ функции-члены шаблонных классов не конкретизируются, пока они не вызваны. Пока не вызывается неправильный оператор С), компилятору все равно. При вызове перегруженного оператора С), не имеющего смысла, компилятор попытается сгенерировать его тело и обнаружит несоответствие. Определяем функтор, получающий параметры типа int и double, и возвращающий значение типа double. Functor<doub1e, TYPELIST 2Cint, doub1e)> myFunctor; вызываем его. генерируется тело оператора operatorC)(double, int). double result = myFunctorC4, 5.6); Неверный вызов. double result = myFunctorC); ошибка! Оператор operatorOC) неверен, потому что он не определен в классе Functorlmp1<doub1e, TYPELlST 2Cint, doub1e)>. Благодаря этому тонкому трюку класс Functor не обязательно частично инициализировать для нуля, одного, двух и больше параметров - это лишь приведет к дублированию кода. Достаточно определить все версии и позволить компилятору генерировать только одну из них. Теперь все вспомогательные средства созданы, и можно приступать к определению конкретных классов, производных от класса Functorlmpl. 5.6. Работа о функторами Начнем работать с функторами. Функторы представляют собой экземпляры классов, в которых определен оператор (), как в классе Functor (т.е. объект класса Functor и сам является функтором). Следовательно, конструктор класса Functor, получающий объект класса Functor в качестве параметра, является шаблоном, параметризованным типом этого функтора. template <typename R, class TList> class Functor ... как и раньше ... publ i с: template <class Fun> FunctorCconst Fun& fun); Для того чтобы реализовать этот конструктор, нам нужен простой шаблонный класс FunctorHandlег, производный от класса Functorlmpl<R, TList>. Этот класс хранит объект типа Fun и передает ему оператор С). Реализуя оператор (), мы прибегнем к описанному выше трюку. Чтобы избежать определения слишком большого количества параметров класса FunctorHandl ег, сделаем шаблонным параметром саму конкретизацию класса Functor. Этот единственный параметр содержит остальные, поскольку он предусматривает внутренний оператор typedef. 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 |