Порождение класса, контролирующего выход за границы массива
В функции try_array() из раздела 16.13, предназначенной для тестирования нашей предыдущей реализации шаблона класса Array, есть две инструкции:
int index = iA.find( find_val );
Type value = iA[ index ];
find() возвращает индекс первого вхождения значения find_val или -1, если значение в массиве не найдено. Этот код некорректен, поскольку в нем не проверяется, что не была возвращена -1. Поскольку -1 находится за границей массива, то каждая инициализация value может привести к ошибке. Поэтому мы создадим подтип Array, который будет контролировать выход за границы массива, – Array_RC и поместим его определение в заголовочный файл Array_RC.h:
#ifndef ARRAY_RC_H
#define ARRAY_RC_H
#include "Array.h"
template <class Type>
class Array_RC : public virtual Array<Type> {
public:
Array_RC( int sz = ArraySize )
: Array<Type>( sz ) {}
Array_RC( const Array_RC& r );
Array_RC( const Type *ar, int sz );
Type& operator[]( int ix );
};
#endif
Внутри определения производного класса каждая ссылка на спецификатор типа шаблона базового должна быть квалифицирована списком формальных параметров:
Array_RC( int sz = ArraySize )
: Array<Type>( sz ) {}
Такая запись неправильна:
// ошибка: Array - это не спецификатор типа
Array_RC( int sz = ArraySize ) : Array( sz ) {}
Единственное отличие поведения класса Array_RC от базового состоит в том, что оператор взятия индекса контролирует выход за границы массива. Во всех остальных отношениях можно воспользоваться уже имеющейся реализацией шаблона класса Array. Напомним, однако, что конструкторы не наследуются, поэтому в Array_RC определен собственный набор из трех конструкторов. Мы сделали класс Array_RC виртуальным наследником класса Array, поскольку предвидели необходимость множественного наследования.
Вот полная реализация функций-членов Array_RC, находящаяся в файле Array_RC.C (определения функций класса Array помещены в заголовочный файл Array.C, поскольку мы пользуемся моделью конкретизации шаблонов с включением, описанной в разделе 16.18):
#include "Array_RC.h"
#include "Array.C"
#include <assert.h>
template <class Type>
Array_RC<Type>::Array_RC( const Array_RC<Type> &r )
: Array<Type>( r ) {}
template <class Type>
Array_RC<Type>::Array_RC( const Type *ar, int sz )
: Array<Type>( ar, sz ) {}
template <class Type>
Type &Array_RC<Type>::operator[]( int ix ) {
assert( ix >= 0 && ix < Array<Type>::_size );
return ia[ ix ];
}
Мы квалифицировали обращения к членам базового класса Array, например к _size, чтобы предотвратить просмотр Array до момента конкретизации шаблона:
Array<Type>::_size;
Мы достигаем этого, включая в обращение параметр шаблона. Таким образом, имена в определении Array_RC разрешаются тогда, когда определяется шаблон (за исключением имен, явно зависящих от его параметра). Если встречается неквалифицированное имя _size, то компилятор должен найти его определение, если только это имя не зависит явно от параметра шаблона. Мы сделали имя _size зависящим от параметра шаблона, предварив его именем базового класса Array<Type>. Теперь компилятор не будет пытаться разрешить имя _size до момента конкретизации шаблона. (В определении класса Array_Sort мы приведем другие примеры использования подобных приемов.)
Каждая конкретизация Array_RC порождает экземпляр класса Array. Например:
Array_RC<string> sa;
конкретизирует параметром string как шаблон Array_RC, так и шаблон Array. Приведенная ниже программа вызывает try_array() (реализацию см. в разделе 16.13), передавая ей объекты подтипа Array_RC. Если все сделано правильно, то выходы за границы массивы будут замечены:
#include "Array_RC.C"
#include "try_array.C"
int main()
{
static int ia[] = { 12,7,14,9,128,17,6,3,27,5 };
cout << "конкретизация шаблона класса Array_RC<int>\n";
try_array( iA );
return 0;
}
После компиляции и запуска программа печатает следующее:
конкретизация шаблона класса Array_RC<int>
try_array: начальные значения массива
( 10 )< 12, 7, 14, 9, 128, 17
6, 3, 27, 5 >
try_array: после присваиваний
( 10 )< 128, 7, 14, 9, 128, 128
6, 3, 27, 3 >
try_array: почленная инициализация
( 10 )< 12, 7, 14, 9, 128, 128
6, 3, 27, 3 >
try_array: после почленного копирования
( 10 )< 12, 7, 128, 9, 128, 128
6, 3, 27, 3 >
try_array: после вызова grow
( 10 )< 12, 7, 128, 9, 128, 128
6, 3, 27, 3, 0, 0
0, 0, 0, 0 >
искомое значение: 5 возвращенный индекс: -1
Assertion failed: ix >= 0 && ix < _size