Анимация
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

ны применять виртуальное наследование от базового класса, имеющего заданное состояние. Это усложняет разработку и делает ее негибкой, поскольку предполагается, что классы пользователя наследуют свойства от библиотечных классов, а не наоборот.

Комбинаторное по своей природе, множественное наследование само по себе нельзя применять для поддержки многовариантных проектных рещений.

1.4. Преимущества шаблонов

Для имитации комбинаторного поведения хорощо подходят шаблоны, поскольку они позволяют генерировать код во время компиляции, основываясь на типах, указанных пользователем.

По способу настройки шаблонные классы принципиально отличаются от обычных. Если нужно реализовать какой-то специальный вариант, можно специализировать (specialize) любые функции-члены шаблонного класса, конкретизировав его. Например, если задан шаблон SmartPtr<T>, можно специализировать любую функцию-член класса SmartPtr<Widget>. Это позволяет хорошо детализировать поведение настраиваемого шаблона.

Более того, для шаблонных классов с несколькими параметрами можно использовать частичную шаблонную специализацию (как мы увидим в главе 2). Это дает возможность специализировать только некоторые из аргументов шаблонного класса. Например, с помошью специализации

template <class т, class и> class SmartPtr { ... };

можно специализировать шаблон SmartPtr<T, и> для класса widget, а также для любого другого типа, используя следуюшую синтаксическую конструкцию: template <cliLss и> class SmartPtr<widget, и> { ... };

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

1. Невозможно специализировать структуру. Используя лишь шаблоны, невозможно специализировать структуру класса (т.е. его данные-члены). Специализировать можно только функции.

2. Специализация функций-членов не масштабируется. Можно специализировать любую функцию-член шаблонного класса с одним шаблонным параметром, но невозможно специализировать отдельные функции-члены для шаблонов с несколькими шаблонными параметрами. Проиллюстрируем это утверждение примером.

template <class т> class widget

void FunC) { ... обобщенная реализация ... }

ok: специализация функции-члена класса widget template <> widget<char>::FunC)

... специализированная реализация ...

template <class т, class u> class Gadget

void FunC) { ... обобщенная реализация ... }



Ошибка! частичная специализация функции-члена

класса Gadget невозможна!

template <class u> void Gadget<char, u>::FunC)

{ ... специализированная реализация ... }

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

Сравним список недостатков множественного наследования со списком недостатков шаблонов. Интересно, что множественное наследование и шаблоны дополняют друг друга. Множественное наследование устроено довольно примитивно; шаблоны, наоборот, обладают богатыми возможностями специализации. Множественное наследование теряет информацию о типах; шаблоны ими изобилуют. Специализация шаблонов, в отличие от множественного наследования, не масштабируется. В шаблонах для функции-члена можно задать только один аргумент по умолчанию и создать неограниченное количество базовых классов.

Все это позволяет предположить, что с помощью комбинирова1ШЯ шаблонов и множественного наследования можно сконструировать очень гибкий механизм, пригодный для создания библиотек, содержащих готовые элементы профаммного обеспечения.

1.5. Стратегии и классы стратегий

Сфатегий и классы сфатегий позволяют реализовать безопасные, эффективные и легко настраиваемые элементы проектных решений. Стратегия (policy) определяет интерфейс обычного или шаблонного класса. Этот интерфейс состоит из определения внуфенних типов, функций-членов и переменных-членов.

Понятие Сфатегий имеет много общего с характеристиками (traits) (Alexandrescu, 2000а), но отличается тем, что в них меньше внимания уделяется типам и больше - поведению. Кроме того, понятие сфатегий, предложенное нами, напоминает стратегии проектирования (Gamma et al., 1995), но в отличие от них классы сфатегий связываются на этапе компиляции.

Например, определим стратегию для создания объектов. Сфатегия Creator описывает шаблонный класс типа т. Этот шаблонный класс должен предоставить функцию-член с именем Create, не имеющую аргументов и возвращаюшую указатель на объект класса т. Это означает, что каждый вызов функции Create должен возвращать указатель на новый объект класса т. Точный режим создания объекта определяется во время реализации стратегии.

Определим несколько классов стратегий, реализующих стратегию Creator. Для этого существует три способа. Во-первых, можно использовать оператор new. Во-вторых, вместо оператора new можно вызвать функцию mall ос (Meyers, 1998b). В-третьих, новые объекты можно создавать, клонируя их прототипы. Рассмотрим эти методы.

template <class т> struct OpNewCreator {

static т* CreateC) {

return new Т;



template <c1ass T> struct MallocCreator {

static T* CreateC) {

void* buf = std:imallocCsizeof(T)); if C!buf) return 0; return newCbuf) T;

template <class T> struct PrototypeCreator {

PrototypeCreator(T* pobj = 0) :pPrototype CpObj)

T* CreateC) {

return pPrototype ? pPrototype ->CloneC) : 0;

T* GetPrototypeC) { return pPrototype ; } void SetPrototypeCT* pObj) {pPrototype = pObj;} private:

T* pPrototype ;

Данная стратегия может иметь сколько угодно реализаций. Реализации стратегий называются классами стратегии. Классы стратегий по отдельности не используются. Они либо наследуются другими классами, либо содержатся в них.

Следует отметить, что, в отличие от классических интерфейсов (коллекций чисто виртуальных функций), интерфейсы стратегий не имеют четкого определения. Стратегии ориентируются на синтаксис, а не сигнатуру. Иными словами, стратегия Creator определяет, какая синтаксическая структура класса считается правильной, и не уточняет, какие именно функции класс должен реализовывать. Например, стратегия Creator не указывает, должна ли функция Create быть статической или виртуальной. Она лишь требует, чтобы шаблонный класс определял функцию-член Create. Кроме того, стратегия Creator выражает пожелание (но не требует), чтобы функция Create возвращала указатель на новый объект. Следовательно, вполне допускается, что в отдельных случаях функция-член Create может возвращать нуль или генерировать исключительную ситуацию.

Для одной стратегии можно реализовать несколько классов. Все они должны соот-бетствовать интерфейсу, определенному стратегией. Затем, как мы вскоре убедимся, пользователь сам выберет, какой класс стратегии следует использовать в более крупных структурах.

Три класса стратегий, определенных выше, имеют разные реализации и даже слегка отличающиеся интерфейсы (например, класс PrototypeCreator имеет две дополнительные функции - GetPrototype и SetPrototype). Однако все они определяют

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



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