использует иерархию классов, может компилироваться отдельно от этой иерархии. Это становится возможным благодаря косвенности, обеспечиваемой указателями (как на объекты, так и на функции).
•
Статический полиморфизм посредством шаблонов также позволяет значению иметь несколько типов. Внутри шаблона
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]).
• Deleter
у tr1::shared_ptr
(см. [C++TR104]).
•
65. Выполняйте настройку явно и преднамеренно
При разработке шаблона точки настройки должны быть написаны корректно, с особой тщательностью, а также ясно прокомментированы. При использовании шаблона необходимо четко знать, как именно следует настроить шаблон для работы с вашим типом, и выполнить соответствующие действия.
Распространенная ловушка при написании библиотек шаблонов заключается в наличии непреднамеренных точек настройки, т.е. точек в вашем шаблоне, где может выполняться поиск пользовательского кода и его использование, но при написании такие действия вами не подразумевались. Попасть в такую ловушку очень легко — достаточно просто вызвать другую функцию или оператор обычным путем (без полной его квалификации), и если окажется, что один из его аргументов имеет тип параметра шаблона (или связанный с ним), то будет начат поиск такого кода, зависящий от аргумента. Примеров тому множество; в частности, см. рекомендацию 58.
Поэтому лучше использовать такие точки преднамеренно. Следует знать три основных пути обеспечения точек настройки в шаблоне, решить, какой именно способ вы хотите использовать в данном месте шаблона, и корректно его закодировать. Затем проверьте, не осталось ли в вашем коде случайных точек настройки там, где вы не предполагали их наличие.
Первый способ создания точки настройки — обычный 'неявный интерфейс' (см. рекомендацию 64), когда ваш шаблон просто рассчитывает на то, что тип имеет соответствующий член с данным именем:
// Вариант 1. Создание точки настройки путем требования от
// типа T 'foo-совместимости', т.е. наличия функции-члена с
// данным именем, сигнатурой и семантикой
template<typename T>
void Sample1(T t) {
t.foo(); // foo - точка настройки
typename T::value_type x; // Еще один пример: создание
} // точки настройки для поиска
// типа (обычно создается посредством typedef)
Для реализации первого варианта автор Sample1
должен выполнить следующие действия.
•
•
Второй вариант представляет собой использование метода 'неявного интерфейса', но с функциями, не являющимися членами, поиск которых выполняется с использованием ADL [3](т.е. ожидается, что данная функция находится в пространстве имен типа, для которого выполняется инстанцирование шаблона). Именно эта ситуация и явилась основной побудительной