Всегда знайте размер шаблона после его расширения
Большинство книг демонстрирует шаблоны типа простого контейнера массива, подобного показаному на листинге 13. Вы не можете использовать здесь наследование (скажем, с базовым классом array, от которого наследуется int_array). Проблема заключается в перегрузке операции operator[](). Вы бы хотели, чтобы она была виртуальной функцией в базовом классе, замещенная затем в производном классе, но сигнатура версии производного класса должна отличаться от сигнатуры базового класса, чтобы все это заработало. Здесь определения функций должны отличаться лишь возвращаемыми типами: int_array::operator[]()
должна возвращать ссылку на тип int, а
long_array::operator[]()
должна возвращать ссылку на тип long, и так далее. Так как время возврата не рассматривается как часть сигнатуры при выборе перегруженной функции, то реализация на основе наследования не жизнеспособна. Единственным решением является шаблон.
Листинг 13. Простой контейнер массива
template class type, int
size
class array
{
type array[size];
public:
class out_of_bounds {}; // возбуждается исключение, если
// индекс за пределами массива
type operator[](int index);
};
template class type, int
size
inline type arraytype, size::operator[](int index)
{
if( 0 = index index size )
return array[ index ]
throw out_of_bounds;
}
Единственная причина осуществимости этого определения заключается в том, что функция-член является встроенной. Если бы этого не было, то вы могли бы получить значительное количество повторяющегося кода. Запомните, что везде далее происходит полное расширение шаблона, включая все функции-члены§. Вследствие того, что каждое из следующих определений на самом деле создает разный тип, то вы должны расширить этот шаблон четыре раза, генерируя четыре идентичные функции operator[](), по одной для каждого расширения шаблона:
arrayint,10 ten_element_array;
arrayint,11 eleven_element_array;
arrayint,12 twelve_element_array;
arrayint,13 thirteen_element_array;
(то
есть
arrayint,10::operator[](), arrayint,11::operator []() и так далее).
Вопрос состоит в том, как сократить до минимума дублирование кода. Что, если мы уберем размер за пределы шаблона, как на листинге 14? Предыдущие объявления теперь выглядят так:
arrayint ten_element_array (10);
arrayint eleven_element_array (11);
arrayint twelve_element_array (12);
arrayint thirteen_element_array (13);
Теперь у нас есть только одно определение класса (и один вариант operator[]()) с четырьмя объектами этого класса.
Листинг 14. Шаблон массива (второй проход)
template class type
class array
{
type *array;
int size;
public:
virtual ~array( void );
array( int size = 128 );
class out_of_bounds {}; //
возбуждается исключение, если
// индекс за пределами массива
type operator[](int index);
};
template class type
arraytype::array( int sz /*= 128*/ ): size(sz)
, array( new type[ sz ] )
{}
template class type
arraytype::~array( void )
{
delete [] array;
}
template class type
inline type arraytype::operator[](int index)
{
if( 0 = index index size )
return array[ index ]
throw out_of_bounds;
}
Главным недостатком этой второй реализации является то, что вы не можете объявить двухмерный массив. Определение на листинге 13 разрешает следующее:
array arrayint, 10, 20 ar;
(20-элементный массив из 10-элементных массивов). Определение на листинге 14 устанавливает размер массива, используя конструктор, поэтому лучшее, что вы можете получить, это:
array arrayint ar2(20);
Внутренний arrayint
создан с использованием конструктора по умолчанию, поэтому это 128-элементный массив; мы объявили 20-элементный массив из 128-элементных массивов.
Вы можете решить эту последнюю проблему при помощи наследования. Рассмотрим следующее определение производного класса:
template class type, int
size
class sized_array : public arraytype
{
public:
sized_array() : arraytype(size) {}
};
Здесь ничего нет, кроме единственной встроенной функции, поэтому это определение очень маленького класса. Оно совсем не будет увеличивать размер программы, вне зависимости от того, сколько раз будет расширен шаблон. Вы теперь можете записать:
sized_array sized_arrayint,10, 20 ar3;
для того, чтобы получить 20-элементный массив из 10-элементных массивов.