Чтение онлайн

ЖАНРЫ

Стандарты программирования на С++. 101 правило и рекомендация

Александреску Андрей

Шрифт:

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

Ссылки

[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] (т.е. ожидается,

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

3

Поиск, зависящий от аргумента (см. стр. 118). — Прим. перев.

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

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

// являющейся членом с данным именем, сигнатурой и

// семантикой, поиск которой выполняется посредством ADL.

// (Это единственный вариант, при котором не требуется поиск

// самого типа T.)

template<typename T>

void Samplе2(T t) {

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

 cout << t; // Еще один пример - operator<< с записью в

} // виде оператора представляет собой такую же

// точку настройки

Для реализации варианта 2 автор

Samplе2
должен выполнить следующие действия.

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

SomeNamespace::foo(t)
) и чтобы у шаблона не было функции-члена с тем же именем, поскольку в обоих этих случаях поиск, зависящий от аргумента, выполняться не будет, что предотвратит поиск имени в пространстве имен, в котором находится тип
T
.

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

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

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

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

// типа T "foo-совместимости" путем специализации шаблона

// SampleTraits<> с предоставлением (обычно статической)

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

Поделиться с друзьями: