18.6. Пример множественного виртуального наследования A
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
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Синтаксис множественного фона
Синтаксис множественного фона Поставить эти четыре изображения в качестве фона элемента body очень просто с использованием нового синтаксиса CSS3:body { background: url(../img/stars-1.png) repeat-x fixed -130% 0, url(../img/stars-2.png) repeat-x fixed 40% 0, url(../img/space-bg.png) repeat-x fixed -80% 0, url(../img/clouds.png) repeat-x fixed 100% 0; background-color: #1a1a1a;
Добавление и настройка виртуального сетевого адаптера
Добавление и настройка виртуального сетевого адаптера Если при создании ВМ вы запретили использование сетевого подключения, выбрав вариант Networking is not required (Сетевое подключение не требуется), то сетевой адаптер в конфигурацию ВМ не включается. Чтобы добавить его,
18.8.6 Доступ к дискам виртуального компьютера из ОС базового
18.8.6 Доступ к дискам виртуального компьютера из ОС базового Во всех трех рассмотренных примерах мы говорили только о том, как получить доступ к дискам базового компьютера из виртуального. Однако можно поставить вопрос и наоборот: как получить доступ к дискам виртуального
Проблема наследования
Проблема наследования Если существует необходимость наследовать от класса Singleton, то следует придерживаться определенных правил.Во-первых, класс-наследник должен переопределить метод Instance(), так, чтобы создавать экземпляр производного класса. Если не предполагается, что
Правило 40: Продумывайте подход к использованию множественного наследования
Правило 40: Продумывайте подход к использованию множественного наследования Когда речь заходит о множественном наследовании (multiple inheritance – MI), сообщество разработчиков на C++ разделяется на два больших лагеря. Одни полагают, что раз одиночное исследование (SI) – это хорошо,
18.5.1. Объявление виртуального базового класса
18.5.1. Объявление виртуального базового класса Для указания виртуального наследования в объявление базового класса вставляется модификатор virtual. Так, в данном примере ZooAnimal становится виртуальным базовым для Bear и Raccoon:// взаимное расположение ключевых слов public и virtual//
18.5.4. Видимость членов виртуального базового класса
18.5.4. Видимость членов виртуального базового класса Изменим наш класс Bear так, чтобы он имел собственную реализацию функции-члена onExhibit(), предоставляемой также ZooAnimal: bool Bear::onExhibit() { ... }Теперь обращение к onExhibit() через объект Bear разрешается в пользу экземпляра, определенного в
19. Применение наследования в C++
19. Применение наследования в C++ При использовании наследования указатель или ссылка на тип базового класса способен адресовать объект любого производного от него класса. Возможность манипулировать такими указателями или ссылками независимо от фактического типа
История одного виртуального убийства Сергей Голубицкий
История одного виртуального убийства Сергей Голубицкий Опубликовано 11 ноября 2013 Аналитики, тем более финансовые, очень редко используют резкие выражения в своих отчётах. О том, чтобы подписывать приговоры, вообще не может быть речи: компании, чей
С чего началась эпидемия виртуального фермерства Михаил Карпов
С чего началась эпидемия виртуального фермерства Михаил Карпов Опубликовано 30 июля 2010 года Жми на корову! Можно, конечно, и на живую, если у неё покладистый нрав, но речь идёт о коровах виртуальных. На них то и дело жмут курсором мыши в новой игре Cow
Смысл наследования
Смысл наследования Мы уже рассмотрели основные способы наследования. Многое еще предстоит изучить, в частности, множественное наследование и детали того, что происходит с утверждениями в контексте наследования (понятие субконтрактов).Но вначале следует поразмышлять
Примеры множественного наследования
Примеры множественного наследования Множественное наследование это, по сути, прямое приложение уже рассмотренных принципов наследования, - класс вправе иметь произвольное число родителей. Однако, изучая этот вопрос более внимательно, можно обнаружить две интересные
Пресс-релиз проекта Виртуального Магистрата[206]
Пресс-релиз проекта Виртуального Магистрата[206] Для срочной публикации, 4 марта 1996 года Учрежден Виртуальный Магистрат для добровольного разрешения споров при сетевых коллизиях по ИнтернетуИНТЕРНЕТ. — Недавно учрежденный проект Виртуального Магистрата будет