Опубликован: 10.10.2006 | Доступ: свободный | Студентов: 6020 / 456 | Оценка: 4.26 / 3.88 | Длительность: 31:30:00
Лекция 8:

Шаблоны типа

8.4.3 Передача операций как параметров функций

Можно не задавать функцию сравнения как часть типа Vector, а передавать ее как второй параметр функции sort(). Этот параметр является объектом класса, в котором определена реализация операции сравнения:

template<class T> void sort(Vector<T>& v, Comparator<T>& cmp)
{
  unsigned n = v.size();

  for (int i = 0; i<n-1; i++)
      for ( int j = n-1; i<j; j--)
          if (cmp.lessthan(v[j],v[j-1])) {
          // меняем местами v[j] и v[j-1]
             T temp = v[j];
             v[j] = v[j-1];
             v[j-1] = temp;
          }
}

Этот вариант можно рассматривать как обобщение традиционного приема, когда операция сравнения передается как указатель на функцию.

Воспользоваться этим можно так:

void f(Vector<int>& vi,
       Vector<String>& vc,
       Vector<int>& vi2,
       Vector<char*>& vs)
{
  Comparator<int> ci;
  Comparator<char*> cs;
  Comparator<String> cc;

  sort(vi,ci);   // sort(Vector<int>&);
  sort(vc,cc);   // sort(Vector<String>&);
  sort(vi2,ci);  // sort(Vector<int>&);
  sort(vs,cs);   // sort(Vector<char*>&);
}

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

8.4.4 Неявная передача операций

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

template<class T> void sort(Vector<T>& v)
  {
    unsigned n = v.size();

    for (int i=0; i<n-1; i++)
        for (int j=n-1; i<j; j--)
            if (Comparator<T>::lessthan(v[j],v[j-1])) {
            // меняем местами v[j] и v[j-1]
               T temp = v[j];
               v[j] = v[j-1];
               v[j-1] = temp;
            }
 }

В результате мы приходим к первоначальному варианту использования sort():

void f(Vector<int>& vi,
        Vector<String>& vc,
        Vector<int>& vi2,
        Vector<char*>& vs)
 {

   sort(vi);   // sort(Vector<int>&);
   sort(vc);   // sort(Vector<String>&);
   sort(vi2);  // sort(Vector<int>&);
   sort(vs);   // sort(Vector<char*>&);
}

Основное преимущество этого варианта, как и двух предыдущих, по сравнению с исходным вариантом в том, что часть программы, занятая собственно сортировкой, отделена от частей, в которых находятся такие операции, работающие с элементами, как, например, lessthan. Необходимость подобного разделения растет с ростом программы, и особенный интерес это разделение представляет при проектировании библиотек. Здесь создатель библиотеки не может знать типы параметров шаблона, а пользователи не знают (или не хотят знать) специфику используемых в шаблоне алгоритмов. В частности, если бы в функции sort() использовался более сложный, оптимизированный и рассчитанный на коммерческое применение алгоритм, пользователь не очень бы стремился написать свою особую версию для типа char*, как это было сделано в \S 8.4.1. Хотя реализация класса Comparator для специального случая char* тривиальна и может использоваться и в других ситуациях.

8.4.5 Введение операций с помощью параметров шаблонного класса

Возможны ситуации, когда неявность связи между шаблонной функцией sort() и шаблонным классом Comparator создает трудности. Неявную связь легко упустить из виду и в то же время разобраться в ней может быть непросто. Кроме того, поскольку эта связь "встроена" в функцию sort(), невозможно использовать эту функцию для сортировки векторов одного типа, если операция сравнения рассчитана на другой тип (см. упражнение 3 в \S 8.9). Поместив функцию sort() в класс, мы можем явно задавать связь с классом Comparator:

template<class T, class Comp> class Sort {
 public:
    static void sort(Vector<T>&);
 };

Не хочется повторять тип элемента, и это можно не делать, если использовать typedef в шаблоне Comparator:

template<class T> class Comparator {
 public:
    typedef T T;  // определение Comparator<T>::T
    static int lessthan(T& a, T& b) {
      return a < b;
    }
    // ...
 };

В специальном варианте для указателей на строки это определение выглядит так:

class Comparator<char*> {
 public:
   typedef char* T;
   static int lessthan(T a, T b) {
     return strcmp(a,b) < 0;
   }
   // ...
 };

После этих изменений можно убрать параметр, задающий тип элемента, из класса Sort:

template<class T, class Comp> class Sort {
 public:
    static void sort(Vector<T>&);
 };

Теперь можно использовать сортировку так:

void f(Vector<int>& vi,
       Vector<String>& vc,
       Vector<int>& vi2,
       Vector<char*>& vs)
{
  Sort< int,Comparator<int> >::sort(vi);
  Sort< String,Comparator<String> >:sort(vc);
  Sort< int,Comparator<int> >::sort(vi2);
  Sort< char*,Comparator<char*> >::sort(vs);
}

и определить функцию sort() следующим образом:

template<class T, class Comp>
void Sort<T,Comp>::sort(Vector<T>& v)
{
   for (int i=0; i<n-1; i++)
       for (int j=n-1; i<j; j--)
           if (Comp::lessthan(v[j],v[j-1])) {
              T temp = v[j];
              v[j] = v[j-1];
              v[j-1] = temp;
           }
}

Последний вариант ярко демонстрирует как можно соединять в одну программу отдельные ее части. Этот пример можно еще больше упростить, если использовать класс сравнителя (Comp) в качестве единственного параметра шаблона. В этом случае в определениях класса Sort и функции Sort::sort() тип элемента будет обозначаться как Comp::T.

Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Кирилл Пшенцов
Кирилл Пшенцов
Россия, г. Москва
Антон Щанкин
Антон Щанкин
Россия