Правила программирования на С и С++. Главы 7-8
Страница 69. Всегда знайте размер шаблона после его расширения


 

156. Всегда знайте размер шаблона после его расширения.

Большинство книг демонстрирует шаблоны типа простого контейнера массива, подобного показанному на листинге 13. Вы не можете использовать здесь наследование (скажем, с базовым классом array, от которого наследуется int_array). Проблема заключается в перегрузке операции operator[](). Вы бы хотели, чтобы она была виртуальной функцией в базовом классе, замещенная затем в производном классе, но сигнатура версии производного класса должна отличаться от сигнатуры базового класса, чтобы все это заработало. Здесь определения функций должны отличаться лишь возвращаемыми типами: int_array::operator[]() должна возвращать ссылку на тип int, а long_array::operator[]() должна возвращать ссылку на тип long, и так далее. Так как время возврата не рассматривается как часть сигнатуры при выборе перегруженной функции, то реализация на основе наследования не жизнеспособна. Единственным решением является шаблон.

Листинг 13. Простой контейнер массива.

  1. template ?class type, int size >
  2. class array
  3. {
  4. type array[size];
  5. public:
  6. class out_of_bounds {}; // возбуждается исключение, если вы используете
  7. // индекс за пределами массива
  8. type ?operator[](int index);
  9. };
  10. template ?class type, int size >
  11. inline type ?array?type, size>::operator[](int index)
  12. {
  13. if( 0 ?= index ?? index ? size )
  14. return array[ index ]
  15. throw out_of_bounds;
  16. }
Единственная причина осуществимости этого определения заключается в том, что функция-член является встроенной. Если бы этого не было, то вы могли бы получить значительное количество повторяющегося кода. Запомните, что везде далее происходит полное расширение шаблона, включая все функции-члены. Вследствие того, что каждое из следующих определений на самом деле создает разный тип, то вы должны расширить этот шаблон четыре раза, генерируя четыре идентичные функции operator[](), по одной для каждого расширения шаблона: array?int,10> ten_element_array;

array?int,11> eleven_element_array;

array?int,12> twelve_element_array;

array?int,13> thirteen_element_array;

( то есть array?int,10>::operator[](),array?int,11>::operator[]() и так далее).

Вопрос состоит в том, как сократить до минимума дублирование кода. Что, если мы уберем размер за пределы шаблона как на листинге 14? Предыдущие объявления теперь выглядят так:

array?int> ten_element_array (10);

array?int> eleven_element_array (11);

array?int> twelve_element_array (12);

array?int> thirteen_element_array (13);

Теперь у нас есть только одно определение класса (и один вариант operator[]()) с четырьмя объектами этого класса. 

Листинг 14. Шаблон массива (второй проход).

  1. template ?class type>
  2. class array
  3. {
  4. type *array;
  5. int size;
  6. public:
  7. virtual ~array( void );
  8. array( int size = 128 );
  9. class out_of_bounds {}; // возбуждается исключение, если вы используете
  10. // индекс за пределами массива
  11. type ?operator[](int index);
  12. };
  13. template ?class type>
  14. array?type>::array( int sz /*= 128*/ ): size(sz)
  15. , array( new type[ sz ] )
  16. {}
  17. template ?class type>
  18. array?type>::~array( void )
  19. {
  20. delete [] array;
  21. }
  22. template ?class type>
  23. inline type ?array?type>::operator[](int index)
  24. {
  25. if( 0 ?= index ?? index ? size )
  26. return array[ index ]
  27. throw out_of_bounds;
  28. }
Главным недостатком этой второй реализации является то, что вы не можете объявить двухмерный массив. Определение на листинге 13 разрешает следующее: array? array?int, 10>, 20> ar;(20-элементный массив из 10-элементных массивов). Определение на листинге 14 устанавливает размер массива, используя конструктор, поэтому лучшее, что вы можете получить, это: array? array?int> > ar2(20);Внутренний array?int> создан с использованием конструктора по умолчанию, поэтому это 128-элементный массив; мы объявили 20-элементный массив из 128-элементных массивов.

Вы можете решить эту последнюю проблему при помощи наследования. Рассмотрим следующее определение производного класса:

template? class type, int size >

class sized_array : public array?type>

{

public:

sized_array() : array?type>(size) {}};Здесь ничего нет, кроме единственной встроенной функции, поэтому это определение очень маленького класса. Оно совсем не будет увеличивать размер программы, вне зависимости от того, сколько раз будет расширен шаблон. Вы теперь можете записать: sized_array? sized_array?int,10>, 20> ar3;

для того, чтобы получить 20-элементный массив из 10-элементных массивов.

 
« Предыдущая статья   Следующая статья »