18.6. Пример множественного виртуального наследования A

We use cookies. Read the Privacy and Cookie Policy

18.6. Пример множественного виртуального наследования A

Мы продемонстрируем определение и использование множественного виртуального наследования, реализовав иерархию шаблонов классов Array (см. раздел 2.4) на основе шаблона Array (см. главу 16), модифицированного так, чтобы он стал конкретным базовым классом. Перед тем как приступать к реализации, поговорим о взаимосвязях между шаблонами классов и наследованием.

Конкретизированный экземпляр такого шаблона может выступать в роли явного базового класса:

class IntStack : private Arrayint {};

Разрешается также произвести его от не шаблонного базового класса:

class Base {};

template class Type

class Derived : public Base {};

Шаблон может выступать одновременно в роли базового и производного классов:

template class Type

class Array_RC : public virtual ArrayType {};

В первом примере конкретизированный типом int шаблон Array служит закрытым базовым классом для не шаблонного IntStack. Во втором примере не шаблонный Base служит базовым для любого класса, конкретизированного из шаблона Derived. В третьем примере любой конкретизированный из шаблона Array_RC класс является производным от класса, конкретизированного из шаблона Array. Так, инструкция

Array_RCint ia;

конкретизирует экземпляры шаблонов Array и Array_RC.

Кроме того, сам параметр-шаблон может служить базовым классом [MURRAY93]:

template typename Type

class Persistent : public Type { ... };

в данном примере определяется производный устойчивый (persistent) подтип для любого конкретизированного типа. Как отмечает Мюррей (Murray), на Type налагается неявное ограничение: он должен быть типом класса. Например, инструкция

Persistent int pi; // ошибка

приводит к ошибке компиляции, поскольку встроенный тип не может быть объектом наследования.

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

template class T class Base {};

то необходимо писать:

template class Type

class Derived : public BaseType {};

Такая запись неправильна:

// ошибка: Base - это шаблон,

// так что должны быть заданы его аргументы

template class Type

class Derived : public Base {};

* В следующем разделе шаблон Array, определенный в главе 16, выступает в роли виртуального базового класса для подтипа Array, контролирующего выход за границы массива; для отсортированного подтипа Array; для подтипа Array, который обладает обоими указанными свойствами. Однако первоначальное определение шаблона класса Array для наследования не подходит: все его члены и вспомогательные функции объявлены закрытыми, а не защищенными;

* ни одна из зависящих от типа функций-членов, скажем оператор взятия индекса, не объявлена виртуальной.

Означает ли это, что наша первоначальная реализация была неправильной? Нет. Она была верной на том уровне понимания, которым мы тогда обладали. При реализации шаблона класса Array мы еще не осознали необходимость специализированных подтипов. Теперь, однако, определение шаблона придется изменить так (реализации функций-членов при этом останутся теми же):

#ifndef ARRAY_H

#define ARRAY_H

#include iostream

template class Type class Array;

template class Type ostream&

operator( ostream &, Array&Type& & );

template class Type

class Array {

static const int ArraySize = 12;

public:

explicit Array( int sz = ArraySize ) { init( 0, sz ); }

Array( const Type *ar, int sz ) { init( ar, sz ); }

Array( const Array &iA ) { init( iA.ia, iA.size()); }

virtual ~Array() { delete[] ia; }

Array& operator=( const Array & );

int size() const { return _size; }

virtual void grow();

virtual void print( ostream& = cout );

Type at( int ix ) const { return ia[ ix ]; }

virtual Type& operator[]( int ix ) { return ia[ix]; }

virtual void sort( int,int );

virtual int find( Type );

virtual Type min();

virtual Type max();

protected:

void swap( int, int );

void init( const Type*, int );

int _size;

Type *ia;

};

#endif

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

int find( const Array int &ia, int value )

{

for ( int ix = 0; ix ia.size(); ++ix )

// а теперь вызов виртуальной функции

if ( ia[ ix ] == value )

return ix;

return -1;

}

Для повышения производительности мы включили встроенную функцию-член at(),обеспечивающую прямое чтение элемента.

18.6.1. Порождение класса, контролирующего выход за границы массива

В функции 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 ArrayType {

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 )

: ArrayType( 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_RCType &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. Теперь компилятор не будет пытаться разрешить имя _size до момента конкретизации шаблона. (В определении класса Array_Sort мы приведем другие примеры использования подобных приемов.)

Каждая конкретизация Array_RC порождает экземпляр класса Array. Например:

Array_RCstring 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 ";

try_array( iA );

return 0;

}

После компиляции и запуска программа печатает следующее:

конкретизация шаблона класса Array_RCint

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