Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Класс массива с множественным наследованием



Определим отсортированный массив с контролем выхода за границы. Для этого можно применить множественное наследование от Array_RC и Array_Sort. Вот как выглядит наша реализация (напомним еще раз, что мы ограничились тремя конструкторами и оператором взятия индекса). Определение находится в заголовочном файле Array_RC_S.h:

#ifndef ARRAY_RC_S_H

#define ARRAY_RC_S_H

 

#include " Array_S.C"

#include " Array_RC.C"

 

template < class Type>

class Array_RC_S: public Array_RC< Type>,

              public Array_Sort< Type>

{

public:

Array_RC_S( int sz = Array< Type>:: ArraySize )

        : Array< Type> ( sz )

         { clear_bit(); }

 

Array_RC_S( const Array_RC_S & rca )

  : Array< Type> ( rca )

      { sort( 0, Array< Type>:: _size-1 ); clear_bit(); }

 

Array_RC_S( const Type* arr, int sz )

  : Array< Type> ( arr, sz )

   { sort( 0, Array< Type>:: _size-1 ); clear_bit(); }

 

Type& operator[]( int index )

   {

    set_bit();

   return Array_RC< Type>:: operator[]( index );

}

};

 

#endif

Этот класс наследует две реализации каждой интерфейсной функции Array: из Array_Sort и из виртуального базового класса Array через Array_RC (за исключением оператора взятия индекса, для которого из обоих базовых классов наследуется замещенный экземпляр). При невиртуальном наследовании вызов find() был бы помечен компилятором как неоднозначный, поскольку он не знает, какой из унаследованных экземпляров мы имели в виду. В нашем случае замещенным в Array_Sort экземплярам отдается предпочтение по сравнению с экземплярами, унаследованными из виртуального базового класса через Array_RC (см. раздел 18.5.4). Таким образом, при виртуальном наследовании неквалифицированный вызов find() разрешается в пользу экземпляра, унаследованного из класса Array_Sort.

Оператор взятия индекса переопределен в классах Array_RC и Array_Sort, и обе реализации имеют равный приоритет. Поэтому внутри Array_RC_S неквалифицированное обращение к оператору взятия индекса неоднозначно. Класс Array_RC_S должен предоставить собственную реализацию, иначе пользователи не смогут напрямую применять такой оператор к объектам этого класса. Но какова семантика его вызова в Array_RC_S? При учете отсортированности массива он должен установить в true унаследованный член dirty_bit. А чтобы учесть наследование от класса с контролем выхода за границы массива – проверить указанный индекс. После этого можно возвращать элемент массива с данным индексом. Последние два шага выполняет унаследованный из Array_RC оператор взятия индекса. При обращении

return Array_RC< Type>:: operator[]( index );

он вызывается явно, и механизм виртуализации не применяется. Поскольку это встроенная функция, то при статическом вызове компилятор подставляет ее код в место вызова.

Теперь протестируем нашу реализацию с помощью функции try_array(), передавая ей по очереди классы, конкретизированные из шаблона Array_RC_S типами int и string:

#include " Array_RC_S.h"

#include " try_array.C"

#include < string>

 

int main()

{

static int ia[ 10 ] = { 12, 7, 14, 9, 128, 17, 6, 3, 27, 5 };

static string sa[ 7 ] = {

    " Eeyore", " Pooh", " Tigger",

    " Piglet", " Owl", " Gopher", " Heffalump"

};

Array_RC_S< int> iA( ia, 10 );

Array_RC_S< string> SA( sa, 7 );

 

cout < < " ê î í ê ð å ò è ç à ö è ÿ ê ë à ñ ñ à Array_RC_S< int> "

    < < endl;

try_array( iA );

 

cout < < " ê î í ê ð å ò è ç à ö è ÿ ê ë à ñ ñ à Array_RC_S< string> "

    < < endl;

try_array( SA );

 

return 0;

}

Вот что печатает программа для класса, конкретизированного типом string (теперь ошибка выхода за границы массива перехватывается):

 

конкретизация класса Array_Sort< string>

 

try_array: начальные значения массива

( 7 )< Eeyore, Gopher, Heffalump, Owl, Piglet, Pooh

  Tigger >

 

try_array: после присваиваний

( 7 )< Eeyore, Gopher, Owl, Piglet, Pooh, Pooh

  Pooh >

 

try_array: почленная инициализация

( 7 )< Eeyore, Gopher, Owl, Piglet, Pooh, Pooh

  Pooh >

 

try_array: после почленного копирования

( 7 )< Eeyore, Piglet, Owl, Piglet, Pooh, Pooh

  Pooh >

 

try_array: после вызова grow

( 7 )< < empty>, < empty>, < empty>, < empty>, Eeyore, Owl

  Piglet, Piglet, Pooh, Pooh, Pooh >

 

искомое значение: Tigger      возвращенный индекс: -1

Assertion failed: ix > = 0 & & ix < size

 

Представленная в этой главе реализация иерархии класса Array иллюстрирует применение множественного и виртуального наследования. Детально проектирование класса массива описано в [NACKMAN94]. Однако, как правило, достаточно класса vector из стандартной библиотеки.

Упражнение 18.16

Добавьте в Array функцию-член spy(). Она запоминает операции, примененные к объекту класса: число доступов по индексу; количество вызовов каждого члена; какой элемент искали с помощью find() и сколько было успешных поисков. Поясните свои проектные решения. Модифицируйте все подтипы Array так, чтобы spy() можно было использовать и для них тоже.

Упражнение 18.17

Стандартный библиотечный класс map (отображение) называют еще ассоциативным массивом, поскольку он поддерживает индексирование значением ключа. Как вы думаете, является ли ассоциативный массив кандидатом на роль подтипа нашего класса Array? Почему?

Упражнение 18.18

Перепишите иерархию Array, пользуясь контейнерными классами из стандартной библиотеки и применяя обобщенные алгоритмы.


 

19

19. Применение наследования в C++

При использовании наследования указатель или ссылка на тип базового класса способен адресовать объект любого производного от него класса. Возможность манипулировать такими указателями или ссылками независимо от фактического типа адресуемого объекта называется полиморфизмом. В этой главе мы рассмотрим три функции языка, обеспечивающие специальную поддержку полиморфизма. Сначала мы познакомимся с идентификацией типов во время выполнения (RTTI – Run-time Type Identification), которая позволяет программе узнать истинный производный тип объекта, адресованного ссылкой или указателем на тип базового класса. Затем расскажем о влиянии наследования на обработку исключений: покажем, как можно определять их в виде иерархии классов и как обработчики для типа базового класса могут перехватывать исключения производных типов. В конце главы мы вернемся к правилам разрешения перегрузки функций и посмотрим, как наследование влияет на то, какие преобразования типов можно применять к аргументам функции, и на выбор наилучшей из устоявших.


Поделиться:



Последнее изменение этой страницы: 2019-04-09; Просмотров: 309; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.021 с.)
Главная | Случайная страница | Обратная связь