Правила программирования на Си и Си++

Шаблоны не заменяют наследование; они его автоматизируют


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

Во-первых, давайте взглянем на то, что не нужно делать. Класс storable, уже использованный мной, снова представляется хорошим примером. Сначала создадим объект collection для управления сохраняемыми объектами:

class collection

{

   storable *head;

public:

// ...

storable *find( const storable a_match_of_this ) const;

};

storable *collection::find( const storable a_match_of_this ) const

{

// Послать сообщение объекту начала списка, указывающее, что спи–

// сок просматривается на совпадение со значением a_match_of_this;



   return  head ? head-find( a_match_of_this )

                : NULL

                ;

}

Механизм поиска нужных объектов скрыт внутри класса storable. Вы можете изменить лежащую в основе структуру данных, поменяв определение storable, и эти изменения совсем не затронут реализацию класса collection.

Затем давайте реализуем класс storable, использующий простой связанный список в качестве лежащей в основе структуры данных:

class storable

{

   storable *next, *prev;

public:

   storable *find ( const storable match_of_this ) const;

   storable *successor ( void ) const;

   virtual int

operator== ( const storable r ) const;

};

storable *storable::find( const storable match_of_this ) const

{

// Возвращает указатель на первый элемент в списке (начиная с

// себя), имеющий тот же ключ, что и match_of_this. Обычно,

// объект-коллекция должен послать это сообщение объекту начала

// списка, указатель на который хранится в классе коллекции.

   storable *current = this;

   for( ; current; current = current-next )

      if( *current == match_of_this )   // найдено совпадение

         return current;

}

storable *storable::successor( void ) const


{
// Возвращает следующее значение в последовательности.
   return next;
}
Функция operator==()
должна быть чисто виртуальной, потому что отсутствует возможность ее реализации на уровне класса storable. Реализация должна быть выполнена в производном классе13
:
class storable_string : public storable
{
  string s;
public:
  virtual int
operator==( const storable r ) const;
// ...
};
virtual int
operator==( const storable r ) const
{
  storable_string *right = dynamic_caststorable_string *( r );
  return right ? (s == r.s) : NULL;
}
Я здесь использовал предложенный в ISO/ANSI Cи++ безопасный механизм нисходящего приведения типов. right
инициализируется значением NULL,
если передаваемый объект (r) не относится к типу storable_string. Например, он может принадлежать к некоторому другому классу, также являющемуся наследником storable.
Пока все идет хорошо. Теперь к проблемам, связанным с шаблонами. Кто-нибудь, не понимающий того, что делает, говорит: "Ребята, я могу исключить наследование и потребность в виртуальных функциях, используя шаблоны", а делает, вероятно, нечто подобное:
template class t_key
class storable
{
   storable *next, *prev;
   t_key key;
public:
// ...
   storable *find      ( const
storable match_me ) const;
   storable *successor ( void                     ) const;
   int       operator==( const
storable r        ) const;
};
template class t_key
int
storablet_key::operator==( const storablet_key r ) const
{
   return key == r.key ;
}
template class t_key
storablet_key *storablet_key::successor( void
) const
{
   return next;
}
template class t_key
storable *storablet_key::find( const storablet_key
                                       match_me ) const
{
   storablet_key *current = this;


   for( ; current; current = current-next )
      if( *current == match_me )       // найдено совпадение
         return current;
}
Проблема здесь в непроизводительных затратах. Функции- члены шаблона класса сами являются шаблонами функций. Когда компилятор расширяет шаблон storable, он также расширяет варианты всех
функций-членов этого шаблона§.
Хотя я их не показал, вероятно, в классе storable
определено множество функций. Многие из этих функций будут похожи в том, что они не используют информацию о типе, передаваемую в шаблон. Это означает, что каждое расширение такой функции будет идентично по содержанию любому другому ее расширению. Из функций, которые не похожи на функцию successor(), большинство будут подобны find(), использующей информацию о типе, но которую легко изменить так, чтобы ее не использовать.
Вы можете решить эту проблему, используя механизм шаблонов для создания производного класса. Основываясь на предыдущей реализации, не использующей шаблоны, вы можете сделать следующее:
template class t_key
class storable_tem : public storable
{
   t_key key;
public:
   // Замещение базового класса
   virtual int
operator==( const storable r ) const;
   // ...
};
template class t_key
/* виртуальный */ int storable_temt_key::operator==( const storable r ) const
{
   t_key *right = dynamic_castt_key *( r );
   return right ? (s == r.s) : NULL;
}
Выбрав другой путь, я сосредоточил в базовом классе все функции, которые не зависят от типа key. Затем я использовал механизм шаблонов для создания определения производного класса, реализующего только те функции, которым нужно знать тип key.
Полезным результатом является существенное сокращение размера кода. Механизм шаблонов может рассматриваться как средство автоматизации производства шаблонных производных классов.
Часть 8и. Исключения

Содержание раздела