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

  • 19.1.3. Класс type_info

  • 19.2.1. Исключения, определенные как иерархии классов

  • 19.2.2. Возбуждение исключения типа класса

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


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

    986
    какой-либо код между выполнением dynamic_cast и проверкой, так что pm будет использоваться только тогда, когда содержит правильный указатель.
    В предыдущем примере операция dynamic_cast преобразует указатель на базовый класс в указатель на производный. Ее также можно применять для трансформации l-значения типа базового класса в ссылку на тип производного. Синтаксис такого использования dynamic_cast следующий: dynamic_cast< Type & >( lval ) где Type& – это целевой тип преобразования, а lval – l-значение типа базового класса.
    Операнд lval успешно приводится к типу Type& только в том случае, когда lval действительно относится к объекту класса, для которого один из производных имеет тип
    Type
    Поскольку нулевых ссылок не бывает (см. раздел 3.6), то проверить успешность выполнения операции путем сравнения результата (т.е. возвращенной оператором dynamic_cast ссылки) с нулем невозможно. Если вместо указателей используются ссылки, условие if ( programmer *pm = dynamic_cast< programmer* >( pe ) ) нельзя переписать в виде if ( programmer &pm = dynamic_cast< programmer& >( pe ) )
    Для извещения об ошибке в случае приведения к ссылочному типу оператор dynamic_cast возбуждает исключение. Следовательно, предыдущий пример можно записать так:
    }
    В случае неудачного завершения ссылочного варианта dynamic_cast возбуждается исключение типа bad_cast. Класс bad_cast определен в стандартной библиотеке; для ссылки на него необходимо включить в программу заголовочный файл .
    (Исключения из стандартной библиотеки мы будем рассматривать в следующем разделе.)
    Когда следует употреблять ссылочный вариант dynamic_cast вместо указательного? Это зависит только от желания программиста. При его использовании игнорировать ошибку приведения типа и работать с результатом без проверки (как в указательном варианте) невозможно; с другой стороны, применение исключений увеличивает накладные расходы во время выполнения программы (см. главу 11).
    #include void company::payroll( employee &re )
    { try { programmer &rm = dynamic_cast< programmer & >( re );
    //
    èñïîëüçîâàòü rm äëÿ âûçîâà programmer::bonus()
    } catch ( std::bad_cast ) {
    //
    èñïîëüçîâàòü ôóíêöèè-÷ëåíû êëàññà employee
    }

    С++ для начинающих
    987
    19.1.2.
    Оператор typeid
    Второй оператор, входящий в состав RTTI, – это typeid, который позволяет выяснить фактический тип выражения. Если оно принадлежит типу класса и этот класс содержит хотя бы одну виртуальную функцию-член, то ответ может и не совпадать с типом самого выражения. Так, если выражение является ссылкой на базовый класс, то typeid сообщает тип производного класса объекта: coiut << typeid( re ).name() << endl;
    Операнд re оператора typeid имеет тип employee. Но так как re – это ссылка на тип класса с виртуальными функциями, то typeid говорит, что тип адресуемого объекта – programmer
    (а не employee, на который ссылается re). Программа, использующая такой оператор, должна включать заголовочный файл , что мы и сделали в этом примере.
    Где применяется typeid? В сложных системах разработки, например при построении отладчиков, а также при использовании устойчивых объектов, извлеченных из базы данных. В таких системах необходимо знать фактический тип объекта, которым программа манипулирует с помощью указателя или ссылки на базовый класс, например для получения списка его свойств во время сеанса работы с отладчиком или для правильного сохранения или извлечения объекта из базы данных. Оператор typeid допустимо использовать с выражениями и именами любых типов. Например, его операндами могут быть выражения встроенных типов и константы. Если операнд не принадлежит к типу класса, то typeid просто возвращает его тип: cout << typeid( 8.16 ).name() << endl; // печатается: double
    Если операнд имеет тип класса, в котором нет виртуальных функций, то typeid возвращает тип операнда, а не связанного с ним объекта: cout << typeid( *pb ).name() << endl; // печатается: Base
    #include programmer pobj; employee &re = pobj;
    //
    ñ ôóíêöèåé name() ìû ïîçíàêîìèìñÿ â ïîäðàçäåëå, ïîñâÿùåííîì type_info
    //
    îíà âîçâðàùàåò C-ñòðîêó "programmer" int iobj; cout << typeid( iobj ).name() << endl; //
    ïå÷àòàåòñÿ: int class Base { /* нет виртуальных функций */ }; class Derived : public Base { /*
    íåò âèðòóàëüíûõ ôóíêöèé */ };
    Derived dobj;
    Base *pb = &dobj;

    С++ для начинающих
    988
    Операнд typeid имеет тип Base, т.е. тип выражения *pb. Поскольку в классе Base нет виртуальных функций, результатом typeid будет Base, хотя объект, на который указывает pb, имеет тип Derived.
    Результаты, возвращенные оператором typeid, можно сравнивать. Например:
    */
    Условие в инструкции if сравнивает результаты применения typeid к операнду, являющемуся выражением, и к операнду, являющемуся именем типа. Обратите внимание, что сравнение typeid( pe ) == typeid( employee* ) возвращает истину. Это удивит пользователей, привыкших писать: pe->salary(); что приводит к вызову виртуальной функции salary() из производного класса manager.
    Поведение typeid(pe) не подчиняется данному механизму. Это связано с тем, что pe – указатель, а для получения типа производного класса операндом typeid должен быть тип класса с виртуальными функциями. Выражение typeid(pe) возвращает тип pe, т.е. указатель на employee. Это значение совпадает со значением typeid(employee*), тогда как все остальные сравнения дают ложь.
    Только при употреблении выражения *pe в качестве операнда typeid результат будет содержать тип объекта, на который указывает pe: typeid( *pe ) == typeid( employee ) // ложно
    В этих сравнениях *pe – выражение типа класса, который имеет виртуальные функции, поэтому результатом применения typeid будет тип адресуемого операндом объекта manager
    Такой оператор можно использовать и со ссылками:
    #include employee *pe = new manager; employee& re = *pe; if ( typeid( pe ) == typeid( employee* ) ) //
    èñòèííî
    //
    ÷òî-òî ñäåëàòü
    /* if ( typeid( pe ) == typeid( manager* ) ) //
    ëîæíî if ( typeid( pe ) == typeid( employee ) ) //
    ëîæíî if ( typeid( pe ) == typeid( manager ) ) //
    ëîæíî
    // вызов виртуальной функции typeid( *pe ) == typeid( manager ) // истинно

    С++ для начинающих
    989
    typeid( &re ) == typeid( manager* ) // ложно
    В первых двух сравнениях операнд re имеет тип класса с виртуальными функциями, поэтому результат применения typeid содержит тип объекта, на который ссылается re. В последних двух сравнениях операнд &re имеет тип указателя, следовательно, результатом будет тип самого операнда, т.е. employee*.
    На самом деле оператор typeid возвращает объект класса типа type_info, который определен в заголовочном файле . Интерфейс этого класса показывает, что можно делать с результатом, возвращенным typeid. (В следующем подразделе мы подробно рассмотрим этот интерфейс.)
    19.1.3.
    Класс type_info
    Точное определение класса type_info зависит от реализации, но некоторые его характерные черты остаются неизменными в любой программе на C++:
    };
    Поскольку копирующие конструктор и оператор присваивания – закрытые члены класса type_info
    , то пользователь не может создать его объекты в своей программе: type_info t2 (typeid( unsigned int ) );
    Единственный способ создать объект класса type_info – воспользоваться оператором typeid
    В классе определены также операторы сравнения. Они позволяют сравнивать два объекта type_info
    , а следовательно, и результаты, возвращенные двумя операторами typeid.
    (Мы говорили об этом в предыдущем подразделе.) typeid( re ) == typeid( manager ) // истинно typeid( re ) == typeid( employee ) // ложно typeid( &re ) == typeid( employee* ) // истинно class type_info {
    //
    ïðåäñòàâëåíèå çàâèñèò îò ðåàëèçàöèè private: type_info( const type_info& ); type_info& operator= ( const type_info& ); public: virtual

    type_info(); int operator==( const type_info& ); int operator!=( const type_info& ); const char * name() const;
    #include type_info t1; //
    îøèáêà: íåò êîíñòðóêòîðà ïî óìîë÷àíèþ
    //
    îøèáêà: êîïèðóþùèé êîíñòðóêòîð çàêðûò

    С++ для начинающих
    990
    typeid( *pe ) != typeid( employee ) // ложно
    Функция name() возвращает C-строку с именем типа, представленного объектом type_info
    . Этой функцией можно пользоваться в программах следующим образом:
    }
    Для работы с функцией-членом name() нужно включить заголовочный файл

    Имя типа – это единственная информация, которая гарантированно возвращается всеми реализациями C++, при этом используется функция-член name() класса type_info. В начале этого раздела упоминалось, что поддержка RTTI зависит от реализации и иногда в классе type_info бывают дополнительные функции-члены. Чтобы узнать, каким образом обеспечивается поддержка RTTI в вашем компиляторе, обратитесь к справочному руководству по нему. Кроме того, можно получить любую информацию, которую компилятор знает о типе, например:

    список функций-членов класса;

    способ размещения объекта в памяти, т.е. взаимное расположение подобъектов базового и производных классов.
    Одним из способов расширения поддержки RTTI является включение дополнительной информации в класс, производный от type_info. Поскольку в классе type_info есть виртуальный деструктор, то оператор dynamic_cast позволяет выяснить, имеется ли некоторое конкретное расширение RTTI. Предположим, что некоторый компилятор предоставляет расширенную поддержку RTTI посредством класса extended_type_info, производного от type_info. С помощью оператора dynamic_cast программа может узнать, принадлежит ли объект типа type_info, возвращенный оператором typeid, к типу extended_type_info. Если да, то пользоваться расширенной поддержкой RTTI разрешено. typeid( re ) == typeid( manager ) //
    èñòèííî
    #include int main() { employee *pe = new manager;
    //
    ïå÷àòàåò: "manager" cout << typeid( *pe ).name() << endl;

    С++ для начинающих
    991
    }
    Если dynamic_cast завершается успешно, то оператор typeid вернет объект класса extended_type_info
    , т.е. компилятор обеспечивает расширенную поддержку RTTI, чем программа может воспользоваться. В противном случае допустимы только базовые средства RTTI.
    Упражнение 19.1
    Дана иерархия классов, в которой у каждого класса есть конструктор по умолчанию и виртуальный деструктор: class D : public X, public C { ... };
    Какие из данных операторов dynamic_cast завершатся неудачно?
    A *pa = dynamic_cast< A* > ( pd );
    C *pc = dynamic_cast< C* > ( pa );
    D *pd = dynamic_cast< D* > ( pb );
    #include
    //
    Файл typeinfo содержит определение типа extended_type_info void func( employee* p )
    {
    // понижающее приведение типа type_info* к extended_type_info* if ( eti *eti_p = dynamic_cast( &typeid( *p ) ) )
    {
    // если dynamic_cast завершается успешно,
    // можно пользоваться информацией из extended_type_info через eti_p
    } else
    {
    // если dynamic_cast завершается неудачно,
    // можно пользоваться только стандартным type_info
    } class X { ... }; class A { ... }; class B : public A { ... }; class C : public B { ... };
    (a) D *pd = new D;
    (b) A *pa = new C;
    (c) B *pb = new B;

    С++ для начинающих
    992
    X *px = dynamic_cast< X* > ( pa );
    Упражнение 19.2
    Объясните, когда нужно пользоваться оператором dynamic_cast вместо виртуальной функции?
    Упражнение 19.3
    Пользуясь иерархией классов из упражнения 19.1, перепишите следующий фрагмент так, чтобы в нем использовался ссылочный вариант dynamic_cast для преобразования *pa в тип D&:
    }
    Упражнение 19.4
    Дана иерархия классов, в которой у каждого класса есть конструктор по умолчанию и виртуальный деструктор: class D : public X, public C { ... };
    Какое имя типа будет напечатано в каждом из следующих случаев: cout << typeid( ra ).name() << endl;
    (d) A *pa = new D; if ( D *pd = dynamic_cast< D* >( pa ) ) {
    // использовать члены D
    } else {
    // использовать члены A class X { ... }; class A { ... }; class B : public A { ... }; class C : public B { ... };
    (a) A *pa = new D; cout << typeid( pa ).name() << endl;
    (b) X *px = new D; cout << typeid( *px ).name() << endl;
    (c) C obj;
    A& ra = cobj; cout << typeid( &ra ).name() << endl;
    (d) X *px = new D;
    A& ra = *px;

    С++ для начинающих
    993
    19.2.
    Исключения и наследование
    Обработка исключений – это стандартное языковое средство для реакции на аномальное поведение программы во время выполнения. C++ поддерживает единообразный синтаксис и стиль обработки исключений, а также способы тонкой настройки этого механизма в специальных ситуациях. Основы его поддержки в языке C++ описаны в главе 11, где показано, как программа может возбудить исключение, передать управление его обработчику (если таковой существует) и как обработчики исключений ассоциируются с try-блоками.
    Возможности механизма обработки исключений становятся больше, если в качестве исключений использовать иерархии классов. В этом разделе мы расскажем, как писать программы, которые умеют возбуждать и обрабатывать исключения, принадлежащие таким иерархиям.
    19.2.1.
    Исключения, определенные как иерархии классов
    В главе 11 мы использовали два типа класса для описания исключений, возбуждаемых функциями-членами нашего класса iStack: class pushOnFull { ... };
    В реальных программах на C++ типы классов, представляющих исключения, чаще всего организуются в группы, или иерархии. Как могла бы выглядеть вся иерархия для этих классов?
    Мы можем определить базовый класс Excp, которому наследуют оба наши класса исключений. Он инкапсулирует данные и функции-члены, общие для обоих производных: class pushOnFull : public Excp { ... };
    Одной из операцией, которые предоставляет базовый класс, является вывод сообщения об ошибке. Эта возможность используется обоими классами, стоящими ниже в иерархии:
    }; class popOnEmpty { ... }; class Excp { ... }; class popOnEmpty : public Excp { ... }; class Excp { public:
    // напечатать сообщение об ошибке static void print( string msg ) { cerr << msg << endl;
    }

    С++ для начинающих
    994
    Иерархию классов исключений разрешается развивать и дальше. От Excp можно произвести другие классы для более точного описания исключений, обнаруживаемых программой: class pushOnFull : public stackExcp { ... }; class divideByZero : public mathExcp { ... };
    Последующие уточнения позволяют более детально идентифицировать аномальные ситуации в работе программы. Дополнительные классы исключений организуются как слои. По мере углубления иерархии каждый новый слой описывает все более специфичные исключения. Например, первый, самый общий слой в приведенной выше иерархии представлен классом Excp. Второй специализирует Excp, выделяя из него два подкласса: stackExcp (для исключений при работе с нашим iStack) и mathExcp (для исключений, возбуждаемых функциями из математической библиотеки). Третий, самый специализированный слой данной иерархии уточняет классы исключений: popOnEmpty и pushOnFull определяют два вида исключений работы со стеком, а ZeroOp и divideByZero
    – два вида исключений математических операций.
    В последующих разделах мы рассмотрим, как возбуждаются и обрабатываются исключения, представленные классами в нашей иерархии.
    19.2.2.
    Возбуждение исключения типа класса
    Теперь, познакомившись с классами, посмотрим, что происходит, когда функция-член push()
    нашего iStack возбуждает исключение:
    }
    Выполнение инструкции throw инициирует несколько последовательных действий:
    1. Инструкция throw создает временный объект типа класса pushOnFull, вызывая его конструктор.
    2. С помощью копирующего конструктора генерируется объект-исключение типа pushOnFull
    – копия временного объекта, полученного на шаге 1. Затем он передается обработчику исключения.
    3. Временный объект, созданный на шаге 1, уничтожается до начала поиска обработчика. class Excp { ... }; class stackExcp : public Excp { ... }; class popOnEmpty : public stackExcp { ... }; class mathExcp : public Excp ( ... }; class zeroOp : public mathExcp { ... }; void iStack::push( int value )
    { if ( full() )
    // value сохраняется в объекте-исключении throw pushOnFull( value );
    // ...

    С++ для начинающих
    995
    Зачем нужно генерировать объект-исключение (шаг 2)? Инструкция throw pushOnFull( value ); создает временный объект, который уничтожается в конце работы throw. Но исключение должно существовать до тех пор, пока не будет найден его обработчик, а он может находиться намного выше в цепочке вызовов. Поэтому необходимо скопировать временный объект в некоторую область памяти(объект-исключение), которая гарантированно существует, пока исключение не будет обработано. Иногда компилятор создает объект-исключение сразу, минуя шаг 1. Однако стандарт этого не требует, да и не всегда такое возможно.
    Поскольку объект-исключение создается путем копирования значения, переданного инструкции throw, то возбужденное исключение всегда имеет такой же тип, как и это значение:
    }
    Выражение *pse имеет тип stackExcp. Тип созданного объекта-исключения – stackExcp
    , хотя pse ссылается на объект с фактическим типом pushOnFull.
    Фактический тип объекта, на который ссылается throw, при создании объекта- исключения не учитывается. Поэтому исключение не будет перехвачено catch- обработчиком pushOnFull.
    Действия, выполняемые инструкцией throw, налагают определенные ограничения на то, какие классы можно использовать для создания объектов-исключений. Оператор throw в функции-члене push() класса iStack вызовет ошибку компиляции, если:

    в классе pushOnFull нет конструктора, принимающего аргумент типа int, или этот конструктор недоступен;

    в классе pushOnFull есть копирующий конструктор или деструктор, но хотя бы один из них недоступен;

    pushOnFull
    – это абстрактный базовый класс. Напомним, что программа не может создавать объекты абстрактных классов (см. раздел 17.1).
    1   ...   85   86   87   88   89   90   91   92   93


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