|
Программирование >> Полиморфизм без виртуальных функций в с++
void к() { max(s,7); max(int(s),7); используется тривиальное преобразование В ARM я предвидел, что возникнет необходимость ослабить правило, запрещающее применение каких бы то ни было преобразований. Многие из ныне существующих компиляторов допускают приведенные выще примеры. Но этот вопрос еще предстоит формально согласовать. 15.6.3.1. Условные выражения в шаблонах При написании щаблона функции иногда желательно, чтобы определение могло зависеть от свойств аргумента шаблона. Так, в [Stroustrup, 1988b] читаем: Рассмотрим, как можно было бы написать функцию печати для типа вектора, которая перед выдачей сортирует элементы, но лишь тогда, когда сортировка возможна. Хорошо бы иметь некоторое средство, которое выясняет, можно ли к объектам данного типа применить данную операцию, скажем, <. Например: template<class Т> void vector<T>::print() { если в Т есть операция <, отсортировать перед печатью if (?Т: :operator<) sortO; for (int i=0; i<sz; i++) { /* ... */ } При печати вектора, элементы которого можно сравнивать, вызывается функция sort О, в противном случае ее вызов пропускается . Я решил не предоставлять такое средство для опроса типа, поскольку был убежден, - как убежден и сейчас, - что оно стало бы причиной написания плохо структурированного кода. В некоторых отношениях эта техника сочетает худшие черты макросов и чрезмерного использования RTTI (см. раздел 14.2.3). Вместо этого для конкретных типов аргументов шаблона можно воспользоваться специализацией (см. раздел 15.10.3). Или же те операции, выполнение которых нельзя гарантировать для всех возможных типов аргументов, вынести в отдельные функции-члены, вызываемые лишь тогда, когда это возможно (см. раздел 15.5). Наконец, можно применить и перегрузку шаблонов функций, чтобы предоставить реализации для разных типов. В качестве примера рассмотрим шаблон функции reverse (), которая изменяет порядок элементов в контейнере на противоположный, если ей переданы итераторы, идентифицирующие первый и последний элементы. Пользовательский код должен был бы вызывать ее так: void f(Listlter<int> 11, Listlter<int> 12, int* pi, int* p2) { reverse(pi,p2); reverse(11,12); где List Iterator используется для доступа к элементам в некотором определенном пользователем контейнере, а с помощью int* можно получать доступ к обычному массиву целых. Чтобы сделать это, для каждого из двух вызовов нужно выполнять различные действия в зависимости от типов аргумента функции reverse(). Шаблон функции reverse () просто выбирает реализацию на основе типа аргумента: template<class Iter> inline void reverse(Iter first. Iter last) { rev(first,last,IterType(first)); Для выбора IterType используется разрешение перегрузки: class RandomAccess { } ; template<class T> inline RandomAccess IterType(T*) { return RandomAccess(); class Forward { }; template<class T> inline Forward IterType(ListIterator<T>) { return Forward0; Здесь для int* будет выбран RandomAccess, a для Listlter - Forward. Тип итератора определяет, какой вариант rev () использовать: template <class Iter> inline void rev(Iter first, Iter last. Forward) { . . . template <class Iter> inline void rev(Iter first. Iter last, RandomAccess) { . . . Заметим, что третий аргумент в rev () фактически не применяется; он нужен только для правильной работы механизма перегрузки. Важно, что каждое свойство типа или алгоритма можно представить типом (бывает, специально определенным для данной цели). Сделав это, мы можем использовать такой тип, чтобы указать механизму перегрузки, какую зависящую от этого свойства функцию выбрать. Если использованный тип не соответствует какому-либо действительно важному свойству, такая техника выглядит несколько искусственной, но остается общей и эффективной. Благодаря встраиванию разрешение делается на стадии компиляции, поэтому подходящая функция rev () будет вызвана напрямую, без каких бы то ни было издержек во время выполнения. Заметьте, что механизм расширяемый, то есть новую реализацию rev () можно добавить, не трогая старый код. Данный при.мер основан на идеях из работы Алекса Степанова [Stepanov,1993]. Иногда может оказаться полезной идентификация типа во время исполнения (см. раздел 14.2.5). 15.7. Синтаксис Первоначально я хотел поместить аргумент шаблона сразу после его и.мени: class vector<class Т> { . . . Но такой синтаксис не всегда мог использоваться с шаблонами функций [Stroustrup, 1988b]: На первый взгляд, синтаксис функций выглядит логично и без нового ключевого слова: Т£с index<class Т> (vector<T>£c v, int i) {/*...*/} Обычно параллель с шаблонами классов не нужна, поскольку аргументы шаблонов функций, как правило, явно не задаются: int i = index(vi,10); char* p = index(vpc,29); Однако из-за такого упрощенного синтаксиса возникают сложности. Объявление шаблона оказывается трудно найти в программе, поскольку его аргументы слишком глубоко размещены в синтаксисе функций и классов, так что разбор шаблонов функций чрезвычайно труден. Можно написать синтаксический анализатор для С++, способный обрабатывать такие объявления шаблонов функций, в которых аргумент используется до того, как определен (см. выше пример с функцией index ()). Я написал такой анализатор, но это было нелегко, а техника разбора там далека от традиционной. Если бы не было введено новое ключевое слово и не требовалось объявлять аргумент шаблона перед его использованием, это привело бы к тем же сложностям, что возникают из-за чересчур запутанного синтаксиса объявлений в С и С++ . В окончательном варианте синтаксис объявления функции index () принимает такой вид: template<class Т> Т& index(vector<Т>& v, int i) {/*...*/ } В to время я серьезно обдумывал вариант синтаксиса, когда возвращаемое значение функции помещается после аргументов. Например: index<class Т>(vector<T>& v, int i) return T& { /* ... */ ) index<class T>(vector<T>& v, int i) : T& {/*...*/ } С помощью данного варианта можно было решить проблемы синтаксического разбора, но многим пользователям удобно, когда имеется отдельное ключевое
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |