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

• Бинарная согласованность. Модули могут компоноваться как статически, так и динамически, до тех пор, пока схемы виртуальных таблиц подчиняются одним и тем же правилам.

Статический полиморфизм посредством шаблонов также позволяет значению иметь несколько типов. Внутри шаблона

template<class T> void f(T t) { /* ... */ }

t может иметь любой тип, который можно подставить в f для получения компилируемого кода. Это называется 'неявным интерфейсом' в противоположность явному интерфейсу базового класса. Таким образом достигается та же цель полиморфизма — написание кода, который работает с разными типами — но совершенно иным путем.

Статический полиморфизм наилучшим образом подходит для решения следующих задач.

• Единообразная работа, основанная на синтаксическом и семантическом интерфейсе. Работа с типами, которые подчиняются синтаксическому и семантическому интерфейсу, может выполняться единообразно. Интерфейсы в данном случае представляют синтаксическую сущность и не основаны на сигнатурах, так что допустима подстановка любого типа, который удовлетворяет данному синтаксису. Например, пусть дана инструкция int i = p->f(5);. Если p — указатель на класс Base, эта инструкция вызывает определенную функцию интерфейса, вероятно, virtual int f(int). Но если p имеет обобщенный тип, то этот вызов может быть связан со множеством различных вещей, включая, например, вызов перегруженного оператора operator->, который возвращает тип, в котором определена функция X f(double) , где X — тип, который может быть преобразован в int.

• Статическая проверка типов. Все типы проверяются статически.

• Статическое связывание (мешает раздельной компиляции). Все типы связываются статически.

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

Определите ваши приоритеты и используйте каждый вид полиморфизма там, где проявляются его сильные стороны.

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

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

template</* ... */> class ConcreteCommand: public Command

В качестве примеров можно привести реализации шаблонов проектирования Command и Visitor (см. [Alexandrescu01] и [Sutter04]).

• Динамика помогает статике. Обобщенный, удобный, статически связываемый интерфейс может использовать внутреннюю динамическую диспетчеризацию, что позволяет обеспечить одинаковую схему размещения объектов. Хорошими примерами могут служить реализации размеченных объединений (см. [Alexandrescu02b] и [Boost]) и параметр Deleter у tr1::shared_ptr (см. [C++TR104]).

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

Ссылки

[Alexandrescu01] §10 • [Alexandrescu02b] • [C++TR104] • [Gamma95] • [Musser01] §1.2-3, §17 • [Stroustrup00] §24.4.1 • [Sutter00] §3 • [Sutter02] §1 • [Sutter04] §17, §35 • [Vandevoorde03] §14 • [Webber03] §8.6

65. Выполняйте настройку явно и преднамеренно

Резюме

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

Обсуждение

Распространенная ловушка при написании библиотек шаблонов заключается в наличии непреднамеренных точек настройки, т.е. точек в вашем шаблоне, где может выполняться поиск пользовательского кода и его использование, но при написании такие действия вами не подразумевались. Попасть в такую ловушку очень легко — достаточно просто вызвать другую функцию или оператор обычным путем (без полной его квалификации), и если окажется, что один из его аргументов имеет тип параметра шаблона (или связанный с ним), то будет начат поиск такого кода, зависящий от аргумента. Примеров тому множество; в частности, см. рекомендацию 58.

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

Первый способ создания точки настройки — обычный 'неявный интерфейс' (см. рекомендацию 64), когда ваш шаблон просто рассчитывает на то, что тип имеет соответствующий член с данным именем:

// Вариант 1. Создание точки настройки путем требования от

// типа T 'foo-совместимости', т.е. наличия функции-члена с

// данным именем, сигнатурой и семантикой

template<typename T>

void Sample1(T t) {

 t.foo();                  // foo - точка настройки

 typename T::value_type x; // Еще один пример: создание

}                          // точки настройки для поиска

                           // типа (обычно создается посредством typedef)

Для реализации первого варианта автор Sample1 должен выполнить следующие действия.

• Вызвать функцию как член. Просто используйте естественный синтаксис вызова функции-члена.

• Документировать точку настройки. Тип должен обеспечить доступную функцию-член foo, которая может быть вызвана с данными аргументами (в данном случае — без аргументов).

Второй вариант представляет собой использование метода 'неявного интерфейса', но с функциями, не являющимися членами, поиск которых выполняется с использованием ADL [3](т.е. ожидается, что данная функция находится в пространстве имен типа, для которого выполняется инстанцирование шаблона). Именно эта ситуация и явилась основной побудительной

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату