class Derived1 : virtual public Vase { ... };
class Derived2 : virtual public Vase { ... };
(b) class VMI : public Derived1, public Derived2 { ... };
(c) class Final : public VMI { ... };
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();