Анимация
JavaScript


Главная  Библионтека 

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

Недостаточная детализация частичной специализации существенно облегчает жизнь разработчикам компиляторов, но затрудняет работу профаммистов. Некоторые из инсфументов, описанных ниже (например, классы intZrype и Туре2туре), специально лредназначены для преодоления этих офаничений частичной специализации.

Частичная специализация шаблонов в книге используется очень широко. Фактически все средства для работы со списками типов (глава 3) созданы с ее помошью.

2.3. Локальные классы

Локальные классы - это интересное и мало изученное свойство языка С++. Эти классы можно определять прямо внутри функций.

void Fun О {

class Local {

... функции-члены

... определения функций-членов ...

... код, использующий локальный класс ...

Существует несколько офаничений - в локальных классах нельзя определять статические переменные-члены и обращаться к нестатическим локальным переменным. Особенно интересно, что локальные классы можно применять внуфи шаблонных функций. Локальный класс, определенный внуфи шаблонной функции, может использовать ее шаблонные параметры.

Шаблонная функция MakeAdapter в приведенном ниже коде адаптирует один интерфейс к другому. Функция MakeAdapter реализует интерфейс на лету с помошью локального класса. Локальный класс хранит члены обобщенного типа.

class interface {

public:

virtual void FunC) =0;

template <class T, class P>

Interface* MakeAdapterCconst T& obj, const P& arg) class Local : punlic Interface public:

Local(const T& obj, const P& arg)

: obj (obj), arg Carg) {} virtual void Fun() {

obj .Can (arg );

private: T obj ; P arg ;

return new Local(obj, arg);



Легко доказать, что любую идиому, использующую локальный класс, можно реализовать с помощью шаблонного класса, определенного вне функции. Иными словами, локальные классы для создания идиом непригодны. С другой стороны, локальные классы могут упростить реализации и улучшить локальность символов.

Однако локальные классы обладают уникальным свойством: они являются терминальными (final). Внешние пользователи не могут создать производные от них классы, скрытые внутри функции. Без локальных классов было бы невозможно добавить неименованное пространство имен в отдельный транслируемый модуль.

В главе 11 локальные классы используются для создания трамплинных функций.

2.4. Отображение целочисленных констант в типы

Простой шаблон, впервые описанный в работе (Alexandrescu, 2000b), может оказаться очень полезным для создания многих идиом обобщенного программирования.

template <int v> struct intZType {

enum { value = v };

Класс intZType генерирует различные типы для разных значений передаваемой ему целочисленной константы. Это происходит потому, что у разных конкретизации шаблона разные типы. Таким образом, тип конкретизации lnt2Type<0> отличается от типа конкретизации lnt2Type<l> и т.д. Кроме того, значение, генерирующее тип, "запоминается" членом value перечисления enum.

Класс lnt2Type применяется, если нужно быстро "типизировать" целочисленную константу. С его помощью можно выбирать разные функции в зависимости от результата вычислений, выполняемых на этапе компиляции. Фактически этот класс обеспечивает статическую диспетчеризацию (compile-time dispatching) константных значений целочисленного типа.

Обычно класс lnt2Type используется, когда одновременно выполняются два условия.

• Нужно вызвать одну из нескольких функций в зависимости от значения статической константы.

• Эту диспетчеризацию нужно выполнить на этапе компиляции.

Для осуществления диспетчеризации во время выполнения программы (nin-time dispatching) можно применять условный оператор if-else или оператор switch. Затраты машинного времени во многих случаях ничтожно малы. Однако часто это сделать не удается. Оператор if-e1se требует, чтобы обе ветки были успешно вычислены, даже если проверяемое условие на этапе компиляции уже известно. Непонятно? Читайте дальше.

Рассмотрим следующую ситуацию. Нам нужно разработать обобщенный контейнер NiftyContainer. Тип его содержимого задается шаблонным параметром.

template <c1ass т> class NiftyContainer

Допустим, что класс NiftyContainer содержит указатели на объекты типа т. Для дублирования объектов, содержащихся в контейнере, нужно вызвать либо конструктор копирования (для неполиморфных типов), либо виртуальную функцию сПопеС) (для полиморфных типов). Эта информация задается в виде булевского шаблонного параметра.



template <typename T, bool isPolymorphio

class NiftyContainer

void DoSomethingO {

T* pSomeObj = ...; if (isPolymorphic)

T* pNewObj = pSomeObj->clone(); ... полиморфный алгоритм ...

else

T* pNewObj = new T(*pSomeObj); ... неполиморфный алгоритм ...

Проблема заключается в том, что компилятор не считает этот код правильным. Например, поскольку полиморфный алгоритм использует функцию pObj->Clone(), функция NiftyContainer: :DoSomething не компилируется, если в классе не определена функция-член с1опе(). Правда, уже во время компиляции становится ясно, какая из ветвей оператора if-else выполняется. Однако компилятору эта информация ни к чему - он усердно пытается компилировать обе ветви, даже если оптимизатор впоследствии исключит тупиковую ветвь кода. Если попытаться вызвать функцию DoSomethi ng для класса Ni f tyContai ner<i nt, false>, компилятор споткнется на вызове функции pObj->с1 опе() и только презрительно фыркнет в ответ.

Неполиморфная ветвь кода также может не пройти компиляцию. Ошибка происходит, если тип т является полиморфным, а неполиморфная ветвь кода пытается создать новый объект с помощью оператора new T(*pObj). Это может произойти потому, что тип т отключает свой конструктор копирования (объявляя его закрытым), как это положено прилежному полиморфному классу.

Было бы прекрасно, если бы компилятор не анализировал тупиковые ветви, но увы! Итак, что можно сделать в этой ситуации?

Получается, что существует большое количество решений, а класс lnt2Type - самое очевидное из них. Он может преобразовывать булевскую переменную isPolymorphic в два разных типа в зависимости от ее значения. Затем можно применить класс lnt2Type<Polymorphic> с простой перегрузкой, и готово!

template <typename т, bool isPolymorphio

class NiftyContainer

private:

void DoSomething(T* pObj, int2Type<true>) {

T* pNewObj = pObj->Clone(); . . . полиморфный алгоритм . . .

void DoSomething(T* pObj, lnt2Type<false>) {

T* pNewObj = new T(*pobj);

. . . неполиморфный алгоритм ...



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