Главная страница
Навигация по странице:

  • 18.6.2. Порождение класса отсортированного массива

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница90 из 93
    1   ...   85   86   87   88   89   90   91   92   93

    964
    Когда два или более экземпляров члена наследуются разными путями (это относится не только к функциям-членам, но и к данным-членам, а также к вложенным типам) и все они представляют один и тот же член виртуального базового класса, неоднозначности не возникает, поскольку существует единственный разделяемый экземпляр (первая категория). Если один экземпляр представляет член виртуального базового, а другой – член унаследованного от него класса, то неоднозначности также не возникает: специализированному экземпляру из производного класса отдается предпочтение по сравнению с разделяемым экземпляром из виртуального базового (вторая категория). Но если оба экземпляра представляют члены производных классов, то прямое обращение неоднозначно. Лучше всего разрешить эту ситуацию, предоставив замещающий экземпляр в производном классе (третья категория).
    Например, при невиртуальном наследовании неквалифицированное обращение к onExhibit()
    через объект Panda неоднозначно: yolo.onExhibit();
    В данном случае все унаследованные экземпляры имеют равные приоритеты при разрешении имени, поэтому неквалифицированное обращение приводит к ошибке компиляции из-за неоднозначности (см. раздел 18.4.1).
    При виртуальном наследовании члену, унаследованному из виртуального базового класса, приписывается меньший приоритет, чем члену с тем же именем, замещенному в производном. Так, унаследованному от Bear экземпляру onExhibit() отдается предпочтение перед экземпляром из ZooAnimal, унаследованному через Raccoon: yolo.onExhibit();
    Если два или более классов на одном и том же уровне наследования замещают некоторый член виртуального базового, то в производном они будут иметь одинаковый вес.
    Например, если в Raccoon также определен член onExhibit(), то при обращении к нему из Panda придется квалифицировать имя с помощью оператора разрешения области видимости:
    }
    Упражнение 18.13
    Дана иерархия классов:
    // ошибка: неоднозначно при невиртуальном наследовании
    Panda yolo( "
    любитель бамбука" );
    // правильно: при виртуальном наследовании неоднозначности нет
    // вызывается Bear::onExhibit() bool Panda::onExhibit()
    { return Bear::onExhibit() &&
    Raccoon::onExhibit() &&
    ! _sleeping;

    С++ для начинающих
    965
    class Final : public MI, public Class { ... };
    (a) В каком порядке вызываются конструкторы и деструкторы при определении объекта
    Final
    ?
    (b) Сколько подобъектов класса Base содержит объект Final? А сколько подобъектов
    Class
    ?
    (c) Какие из следующих присваиваний вызывают ошибку компиляции?
    (ii) pc = new Final; (iv) pd2 = pmi;
    Упражнение 18.14
    Дана иерархия классов: class Class { ... }; class Base : public Class { ... }; class Derived1 : virtual public Base { ... }; class Derived2 : virtual public Base { ... }; class MI : public Derived1, public Derived2 { ... };
    Base *pb;
    MI *pmi;
    Class *pc;
    Derived2 *pd2;
    (i) pb = new Class; (iii) pmi = pb;

    С++ для начинающих
    966
    class VMI : public Derived1, public Derived2 {};
    К каким из унаследованных членов можно обращаться из класса VMI, не квалифицируя имя? А какие требуют квалификации?
    Упражнение 18.15
    Дан класс Base с тремя конструкторами:
    };
    Определите соответствующие конструкторы для каждого из следующих классов:
    (c) class Final : public VMI { ... }; class Base { public: bar( int );
    // ... protected: int ival;
    // ...
    }; class Derived1 : virtual public Base { public: bar( char ); foo( char );
    // ... protected: char cval;
    // ...
    }; class Derived2 : virtual public Base { public: foo( int );
    // ... protected: int ival; char cval;
    // ...
    }; class Base { public:
    Base();
    Base( string );
    Base( const Base& );
    // ... protected: string _name;
    (a) любой из class Derived1 : virtual public Vase { ... }; class Derived2 : virtual public Vase { ... };
    (b) class VMI : public Derived1, public Derived2 { ... };

    С++ для начинающих
    967
    18.6.
    Пример множественного виртуального наследования A
    Мы продемонстрируем определение и использование множественного виртуального наследования, реализовав иерархию шаблонов классов Array (см. раздел 2.4) на основе шаблона Array (см. главу 16), модифицированного так, чтобы он стал конкретным базовым классом. Перед тем как приступать к реализации, поговорим о взаимосвязях между шаблонами классов и наследованием.
    Конкретизированный экземпляр такого шаблона может выступать в роли явного базового класса: class IntStack : private Array {};
    Разрешается также произвести его от не шаблонного базового класса: class Derived : public Base {};
    Шаблон может выступать одновременно в роли базового и производного классов: class Array_RC : public virtual Array {};
    В первом примере конкретизированный типом int шаблон Array служит закрытым базовым классом для не шаблонного IntStack. Во втором примере не шаблонный Base служит базовым для любого класса, конкретизированного из шаблона Derived. В третьем примере любой конкретизированный из шаблона Array_RC класс является производным от класса, конкретизированного из шаблона Array. Так, инструкция
    Array_RC ia; конкретизирует экземпляры шаблонов Array и Array_RC.
    Кроме того, сам параметр-шаблон может служить базовым классом [MURRAY93]: class Persistent : public Type { ... }; в данном примере определяется производный устойчивый (persistent) подтип для любого конкретизированного типа. Как отмечает Мюррей (Murray), на Type налагается неявное ограничение: он должен быть типом класса. Например, инструкция
    Persistent< int > pi; // ошибка class Base {}; template template template < typename Type >

    С++ для начинающих
    968
    приводит к ошибке компиляции, поскольку встроенный тип не может быть объектом наследования.
    Шаблон, выступающий в роли базового класса, должен квалифицироваться полным списком параметров. Если имеется определение: template class Base {}; то необходимо писать: class Derived : public Base {};
    Такая запись неправильна: class Derived : public Base {};
    В следующем разделе шаблон Array, определенный в главе 16, выступает в роли виртуального базового класса для подтипа Array, контролирующего выход за границы массива; для отсортированного подтипа Array; для подтипа Array, который обладает обоими указанными свойствами. Однако первоначальное определение шаблона класса
    Array для наследования не подходит:

    все его члены и вспомогательные функции объявлены закрытыми, а не защищенными;

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

    С++ для начинающих
    969
    #endif
    Одна из проблем, связанных с таким переходом к полиморфизму, заключается в том, что реализация оператора взятия индекса перестала быть встроенной и сводится теперь к значительно более дорогому вызову виртуальной функции. Так, в следующей функции, на какой бы тип она ни ссылалась, было бы достаточно встроенного чтения элемента:
    }
    #ifndef ARRAY_H
    #define ARRAY_H
    #include
    // необходимо для опережающего объявления operator<< template class Array; template ostream& operator<<( ostream &, Array & ); template 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;
    }; int find( const Array< int > &ia, int value )
    { for ( int ix = 0; ix < ia.size(); ++ix )
    // а теперь вызов виртуальной функции if ( ia[ ix ] == value ) return ix; return -1;

    С++ для начинающих
    970
    Для повышения производительности мы включили встроенную функцию-член at()
    ,обеспечивающую прямое чтение элемента.
    18.6.1.
    Порождение класса, контролирующего выход за
    границы массива
    В функции try_array() из раздела 16.13, предназначенной для тестирования нашей предыдущей реализации шаблона класса Array, есть две инструкции:
    Type value = iA[ index ]; find()
    возвращает индекс первого вхождения значения find_val или -1, если значение в массиве не найдено. Этот код некорректен, поскольку в нем не проверяется, что не была возвращена -1. Поскольку -1 находится за границей массива, то каждая инициализация value может привести к ошибке. Поэтому мы создадим подтип Array, который будет контролировать выход за границы массива, – Array_RC и поместим его определение в заголовочный файл Array_RC.h:
    #endif
    Внутри определения производного класса каждая ссылка на спецификатор типа шаблона базового должна быть квалифицирована списком формальных параметров:
    : Array( sz ) {}
    Такая запись неправильна:
    Array_RC( int sz = ArraySize ) : Array( sz ) {} int index = iA.find( find_val );
    #ifndef ARRAY_RC_H
    #define ARRAY_RC_H
    #include "Array.h" template class Array_RC : public virtual Array { public:
    Array_RC( int sz = ArraySize )
    : Array( sz ) {}
    Array_RC( const Array_RC& r );
    Array_RC( const Type *ar, int sz );
    Type& operator[]( int ix );
    };
    Array_RC( int sz = ArraySize )
    // ошибка: Array - это не спецификатор типа

    С++ для начинающих
    971
    Единственное отличие поведения класса Array_RC от базового состоит в том, что оператор взятия индекса контролирует выход за границы массива. Во всех остальных отношениях можно воспользоваться уже имеющейся реализацией шаблона класса Array.
    Напомним, однако, что конструкторы не наследуются, поэтому в Array_RC определен собственный набор из трех конструкторов. Мы сделали класс Array_RC виртуальным наследником класса Array, поскольку предвидели необходимость множественного наследования.
    Вот полная реализация функций-членов Array_RC, находящаяся в файле Array_RC.C
    (определения функций класса Array помещены в заголовочный файл Array.C, поскольку мы пользуемся моделью конкретизации шаблонов с включением, описанной в разделе
    16.18):
    }
    Мы квалифицировали обращения к членам базового класса Array, например к _size, чтобы предотвратить просмотр Array до момента конкретизации шаблона:
    Array::_size;
    Мы достигаем этого, включая в обращение параметр шаблона. Таким образом, имена в определении Array_RC разрешаются тогда, когда определяется шаблон (за исключением имен, явно зависящих от его параметра). Если встречается неквалифицированное имя
    _size
    , то компилятор должен найти его определение, если только это имя не зависит явно от параметра шаблона. Мы сделали имя _size зависящим от параметра шаблона, предварив его именем базового класса Array. Теперь компилятор не будет пытаться разрешить имя _size до момента конкретизации шаблона. (В определении класса Array_Sort мы приведем другие примеры использования подобных приемов.)
    Каждая конкретизация Array_RC порождает экземпляр класса Array. Например:
    Array_RC sa; конкретизирует параметром string как шаблон Array_RC, так и шаблон Array.
    Приведенная ниже программа вызывает try_array() (реализацию см. в разделе 16.13), передавая ей объекты подтипа Array_RC. Если все сделано правильно, то выходы за границы массивы будут замечены:
    #include "Array_RC.h"
    #include "Array.C"
    #include template
    Array_RC::Array_RC( const Array_RC &r )
    : Array( r ) {} template
    Array_RC::Array_RC( const Type *ar, int sz )
    : Array( ar, sz ) {} template
    Type &Array_RC::operator[]( int ix ) { assert( ix >= 0 && ix < Array::_size ); return ia[ ix ];

    С++ для начинающих
    972
    }
    После компиляции и запуска программа печатает следующее: конкретизация шаблона класса Array_RC 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
    18.6.2.
    Порождение класса отсортированного массива
    Вторая наша специализация класса Array – отсортированный подтип Array_Sort. Мы поместим его определение в заголовочный файл Array_S.h:
    #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\n"; try_array( iA ); return 0;

    С++ для начинающих
    973
    #endif
    Array_Sort включает дополнительный член – dirty_bit. Если он установлен в true, то не гарантируется, что массив по-прежнему отсортирован. Предоставляется также ряд вспомогательных функций доступа: is_dirty() возвращает значение dirty_bit; set_bit()
    устанавливает dirty_bit в true; clear_bit() сбрасывает dirty_bit в false
    ; check_bit() пересортировывает массив, если dirty_bit равно true, после чего сбрасывает его в false. Все операции, которые потенциально могут перевести массив в неотсортированное состояние, вызывают set_bit().
    При каждом обращении к шаблону Array необходимо указывать полный список параметров.
    Array::print( os );
    #ifndef ARRAY_S_H_
    #define ARRAY_S_H_
    #include "Array.h" template class Array_Sort : public virtual Array { protected: void set_bit() { dirty_bit = true; } void clear_bit() { dirty_bit = false; } void check_bit() { if ( dirty_bit ) { sort( 0, Array::_size-1 ); clear_bit();
    }
    } public:
    Array_Sort( const Array_Sort& );
    Array_Sort( int sz = Array::ArraySize )
    : Array( sz )
    { clear_bit(); }
    Array_Sort( const Type* arr, int sz )
    : Array( arr, sz )
    { sort( 0,Array::_size-1 ); clear_bit(); }
    Type& operator[]( int ix )
    { set_bit(); return ia[ ix ]; } void print( ostream& os = cout ) const
    { check_bit(); Array::print( os ); }
    Type min() { check_bit(); return ia[ 0 ]; }
    Type max() { check_bit(); return ia[ Array::_size-1 ]; } bool is_dirty() const { return dirty_bit; } int find( Type ); void grow(); protected: bool dirty_bit;
    };

    С++ для начинающих
    974
    вызывает функцию-член print() базового класса Array, конкретизированного одновременно с Array_Sort. Например:
    Array_Sort sas; конкретизирует типом string оба шаблона: Array_Sort и Array. cout << sas; конкретизирует оператор вывода из класса Array, конкретизированного типом string, затем этому оператору передается строка sas. Внутри оператора вывода инструкция ar.print( os ); приводит к вызову виртуального экземпляра print() класса Array_Sort, конкретизированного типом string. Сначала вызывается check_bit(), а затем статически вызывается функция-член print() класса Array, конкретизированного тем же типом. (Напомним, что под статическим вызовом понимается разрешение функции на этапе компиляции и – при необходимости – ее подстановка в место вызова.) Виртуальная функция обычно вызывается динамически в зависимости от фактического типа объекта, адресуемого ar. Механизм виртуализации подавляется, если она вызывается явно с помощью оператора разрешения области видимости, как в Array::print(). Это повышает эффективность в случае, когда мы явно вызываем экземпляр виртуальной функции базового класса из экземпляра той же функции в производном, например в print()
    из класса Array_Sort (см. раздел 17.5).
    Функции-члены, определенные вне тела класса, помещены в файл Array_S.C.
    Объявление может показаться слишком сложным из-за синтаксиса шаблона. Но, если не считать списков параметров, оно такое же, как и для обычных классов:
    }
    Каждое использование имени шаблона в качестве спецификатора типа должно быть квалифицировано полным списком параметров. Следует писать:
    Array_Sort( const Array_Sort &as ) а не template
    Array_Sort::
    Array_Sort( const Array_Sort &as )
    : Array( as )
    {
    // замечание: as.check_bit() не работает!
    // ---- объяснение см. ниже ... if ( as.is_dirty() ) sort( 0, Array::_size-1 ); clear_bit(); template
    Array_Sort::

    С++ для начинающих
    975
    Array_Sort( // ошибка: это не спецификатор типа поскольку второе вхождение Array_Sort синтаксически является именем функции, а не спецификатором типа.
    Есть две причины, по которым правильна такая запись: sort( 0, _size ); а не просто as.check_bit();
    Первая причина связана с типизацией: check_bit() – это неконстантная функция-член, которая модифицирует объект класса. В качестве аргумента передается ссылка на константный объект. Применение check_bit() к аргументу as нарушает его константность и потому воспринимается компилятором как ошибка.
    Вторая причина: копирующий конструктор рассматривает массив, ассоциированный с as
    , только для того, чтобы выяснить, нуждается ли вновь созданный объект класса
    Array_Sort в сортировке. Напомним, однако, что член dirty_bit нового объекта еще не инициализирован.
    К началу выполнения тела конструктора
    Array_Sort инициализированы только члены ia и _size, унаследованные от класса Array. Этот конструктор должен с помощью clear_bit() задать начальные значения дополнительных членов и, вызвав sort(), обеспечить специальное поведение подтипа.
    Конструктор Array_Sort можно было бы инициализировать и по-другому:
    }
    Ниже приведена реализация функции-члена grow().
    1
    Наша стратегия состоит в том, чтобы воспользоваться имеющейся в базовом классе Array реализацией для выделения дополнительной памяти, а затем пересортировать элементы и сбросить dirty_bit:
    1
    Здесь есть потенциальная опасность появления висячей ссылки, если пользователь сохранит адрес какого-либо элемента исходного массива перед тем, как grow() скопирует массив в новую область памяти. См. статью Тома
    Каргилла в [LIPPMAN96b]. template
    Array_Sort:: if ( as.is_dirty() )
    // альтернативная реализация template
    Array_Sort::
    Array_Sort( const Array_Sort &as )
    : Array( as )
    { dirty_bit = as.dirty_bit; clear_bit();

    С++ для начинающих
    1   ...   85   86   87   88   89   90   91   92   93


    написать администратору сайта