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

основанного на стратегиях. Эти механизмы называются стратегиями, а их реализации, соответственно, называются классами стратегий.

Механизм стратегии основан на комбинации шаблонов и множественного наследования. Класс, используюший стратегии (главный класс), представляет собой шаблон со многими шаблонными параметрами (часто называемыми шаблонными шаблонными параметрами), каждый из которых является стратегией. Главный класс "переадресовывает" части функциональных возможностей стратегиям и действует в качестве хранилища нескольких согласованных между собой стратегий.

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

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

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

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

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



Приемы программирования

в этой главе описывается множество приемов программирования на языке С++, используемых на протяжении всей книги. Поскольку эти приемы оказываются полезными в разных ситуациях, они описываются в максимально общем виде, чтобы их легко было применить повторно. Таким образом, один и тот же прием может использоваться в разных ситуациях. Некоторые из этих приемов, например, частичная специализация шаблонов, представляют собой особенности языка программирования. Другие, например, диагностические утверждения на этапе компиляции (compile-time assertions), программируются отдельно.

В этой главе рассматриваются следующие приемы и инструменты программирования.

• Статическая проверка диагностических утверждений.

• Частичная специализация шаблонов.

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

• Отображения между типами и значениями (шаблонные классы intZType и Туре2туре).

• Шаблонный класс Select, средство для выбора типа на этапе компиляции на основе проверки логического выражения.

• Распознавание конвертируемости и наследования на этапе компиляции.

• Класс Typelnfo, удобная оболочка для класса std: :type info.

• Класс Traits, коллекция характеристик (traits), применимых к любому типу в языке С++.

Взятые по отдельности, каждый прием и соответствующий ему код могут выглядеть тривиально. Обычно их размер не превышает 5-10 вполне понятных строк. Однако эти приемы программирования обладают важной особенностью: они "нетерминальны", т.е. их можно комбинировать друг с другом, генерируя идиомы высокого уровня. В совокупности они образуют солидную основу для служебной библиотеки, позволяющей создавать мощные архитектурные конструкции.

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



2.1. Статическая проверка диагностических утверждений

с появлением обобщенного программирования на языке С++ возникла необходимость в более соверщенных средствах статической проверки (и более конкретных сообщениях об ошибках).

Допустим, что вы разрабатываете функцию для безопасного приведения типов (safe casting). Вам нужно привести один тип к другому, не потеряв при этом информацию, причем типы большего размера не должны преобразовываться в типы меньшего размера.

template <class To, class from>

To safe reinterpret castCFrom from)

assertCsizeof(From) <= sizeofCTo)); return reinterpret cast<To>(f rom);

Эта функция вызывается с помощью точно такой же синтаксической конструкции, как и встроенное приведение типов в языке С++.

int i = ... ;

char* р = safe reinterpret cast<char*>(i);

Шаблонный аргумент то указывается явно, а компилятор самостоятельно выводит шаблонный аргумент From, анализируя тип переменной i. Сравнивая размеры, можно убедиться, что тип, к которому выполняется приведение, способен хранить все биты величины приводимого типа. Таким образом, указанный выше код либо выполняет вроде бы правильное приведение типов, либо проверяет диагностическое утверждение в ходе выполнения программы.

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

Однако все не так безнадежно. Выражения, подлежащие вычислению, являются статическими константами (compile-time constant). Это означает, что их проверку можно осуществлять на этапе компиляции, а не в ходе выполнения профаммы. Идея этого подхода заключается в следующем. Компилятору передается языковая конструкция, являющаяся допустимой, если значение выражения не равно нулю, и недопустимой в противном случае. Таким образом, получив выражение, значение которого равно нулю, компилятор выдаст сообщение об ошибке.

Простейшее решение задачи статической проверки диагностических утверждений (Van Horn, 1997) одинаково хорошо работает как в языке С, так и в языке С++, и основано на том, что массивы нулевой длины запрещены.

#define STATic CHECKCexpr) { char unnamedCCexpr) ? 1 : 0]; } Теперь напишем следующий фрагмент.

template <class To, class From>

TO safe reinterpret castCFrom from)

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



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