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

В случае множественного наследования объект производного класса содержит по одному подобъекту каждого из своих базовых (см. раздел 17.3). Например, когда мы пишем

Panda ying_yang;

то объект ying_yang будет состоять из подобъекта класса Bear (который в свою очередь содержит подобъект ZooAnimal), подобъекта Endangered и нестатических членов, объявленных в самом классе Panda, если таковые есть (см. рис. 18.3).

Рис. 18.3. Иерархия множественного наследования класса Panda

Конструкторы базовых классов вызываются в порядке объявления в списке базовых классов. Например, для ying_yang эта последовательность такова: конструктор Bear (но поскольку класс Bear – производный от ZooAnimal, то сначала вызывается конструктор ZooAnimal), затем конструктор Endangered и в самом конце конструктор Panda.

Как отмечалось в разделе 17.4, на порядок вызова не влияет ни наличие базовых классов в списке инициализации членов, ни порядок их перечисления. Иными словами, если бы конструктор Bear вызывался неявно и потому не был бы упомянут в списке инициализации членов, как в следующем примере:

// конструктор по умолчанию класса Bear вызывается до

// конструктора класса Endangered с двумя аргументами ...

Panda::Panda()

: Endangered( Endangered::environment,

Endangered::critical )

{ ... }

то все равно конструктор по умолчанию Bear был бы вызван раньше, чем явно заданный в списке конструктор класса Endangered с двумя аргументами.

Порядок вызова деструкторов всегда противоположен порядку вызова конструкторов. В нашем примере деструкторы вызываются в такой последовательности: ~Panda(), ~Endangered(), ~Bear(), ~ZooAnimal().

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

Однако такую ошибку вызывает не потенциальная неоднозначность неквалифицированного доступа к одному из двух одноименных членов, а лишь попытка фактического обращения к нему (см. раздел 17.4). Например, если в обоих классах Bear и Endangered определена функция-член print(), то инструкция

ying_yang.print( cout );

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

Error: ying_yang.print( cout ) -- ambiguous, one of

Bear::print( ostream& )

Endangered::print( ostream&, int )

Ошибка: ying_yang.print( cout ) -- неоднозначно, одна из

Bear::print( ostream& )

Endangered::print( ostream&, int )

Причина в том, что унаследованные функции-члены не образуют множество перегруженных функций внутри производного класса (см. раздел 17.3). Поэтому print() разрешается только по имени, а не по типам фактических аргументов. (О том, как производится разрешение, мы поговорим в разделе 18.4.)

В случае одиночного наследования указатель, ссылка или объект производного класса при необходимости автоматически преобразуются в указатель, ссылку или объект базового класса, которому открыто наследует производный. Это остается верным и для множественного наследования. Так, указатель, ссылку или сам объект класса Panda можно преобразовать в указатель, ссылку или объект ZooAnimal, Bear или Endangered:

extern void display( const Bear& );

extern void highlight( const Endangered& );

Panda ying_yang;

display( ying_yang ); // i?aaeeuii

highlight( ying_yang ); // i?aaeeuii

extern ostream&

operator( ostream&, const ZooAnimal& );

cout ying_yang endl; // правильно

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

Перейти на страницу:

Похожие книги