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

  • 11.1 Оператор dynamic_cast

  • 11.2 Оператор typeid

  • ттттт. Объектноориентированное программирование


    Скачать 1.73 Mb.
    НазваниеОбъектноориентированное программирование
    Анкорттттт
    Дата30.10.2021
    Размер1.73 Mb.
    Формат файлаpdf
    Имя файлаOOP-PrePrint.pdf
    ТипКонспект
    #259341
    страница9 из 15
    1   ...   5   6   7   8   9   10   11   12   ...   15

    10.6 Исключительные ситуации в конструкторах и деструкторах
    Конструкторы не могут возвращать коды ошибок, соответственно ис- ключения – это единственный метод, чтобы понять, что в конструкторе что- то пошло не так. Однако, необходимо правильно обрабатывать исключи- тельные ситуации в таких случаях. Особенно если в конструкторе формиру- ются объекты в динамической памяти.
    Пример исключительной ситуации в конструкторе: class Test { public:

    92
    Test() { std::cout << "Test::Test()" << std::endl;
    // Здесь, в соответствии с RAII, захватили ресурсы if ( 1 ) { throw std::runtime_error( "AAAAAAAA" );
    } else {}
    }

    Test() { std::cout << "Test::Test()" << std::endl;
    // А здесь мы освобождаем те самые важные ресурсы...
    }
    }; int main() {
    Test* t = 0; try { t = new Test(); // Вроде бы создали...
    } catch ( const std::exception& exc ) { std::cout << exc.what() << std::endl;
    } delete t; // Удалили? Ну-ну... return 0;
    }
    Не стоит пытаться передавать исключения за тело конструктора, т.к. иначе, даже если мы не забываем вызвать delete
    , он не будет вызывать де- структор неправильно созданного объекта. В этом случае, никакие ресурсы, которые были инициализированы в конструкторе до вызова исключения
    (связи с БД, открытые файлы, и т.п.) уже никогда не будут освобождены
    (если они создавались в динамической памяти).
    Вместо указателей на объекты в динамической памяти можно использо- вать один из множества типов «умных указателей», которые удаляются ав- томатически при выходе из области видимости (как локальные объекты):
    // С указателями на объекты в
    // динамической памяти: class Cnt { private:
    X *xa;
    X *xb; public:
    Cnt(int a, int b) { cout << "Cnt::Cnt" << endl; xa = new X(a); xb = new X(b);
    }
    Cnt() { cout << "Cnt::Cnt" << endl; delete xa; delete xb;
    }
    };
    // С умными указателями: class Cnt { private: auto_ptr ia; auto_ptr ib; public:
    Cnt(int a, int b) : ia(new
    X(a)), ib(new X(b)) { cout << "Cnt::Cnt" << endl;
    }
    Cnt() { cout << "Cnt::Cnt" << endl;
    }
    };

    93 std::auto_ptr<>
    – реализует семантику владения. Так называемое раз- рушающее копирование – при присваивании, объект передается от одного указателя другому, удаляясь у первого, чтобы не удалять объект дважды при выходе из области видимости.
    Пример исключительной ситуации в деструкторе: class test
    { public: test() { }
    test(){ throw std::runtime_error("Game over!");
    }
    }; int main() { try { test t; throw std::runtime_error("Error!");
    } catch(std::exception const&)
    { } return 0;
    }
    Когда исключение покидает блок, все локальные объекты, созданные в этом блоке, уничтожаются. Если деструктор объекта, уничтожаемого во время развертки стека, генерирует исключение, то программа будет завер- шена досрочно, и ее уже ничего не спасет – вызывается функция terminate
    Исключение при раскрутке стека всегда будет инициализировать функ- цию terminate()
    – обрабатывайте все ошибки деструктора внутри деструк- тора! Ни в коем случае не инициализируйте исключение!

    94
    11.
    ИДЕНТИФИКАЦИЯ ТИПОВ ВО ВРЕМЯ ВЫПОЛНЕНИЯ
    RTTI

    идентификация типов во время выполнения (Run-time Type
    Identification), позволяет программам, которые манипулируют объектами через указатели или ссылки на базовые классы, получить истинный произ- водный тип адресуемого объекта. Для поддержки RTTI в языке C++ есть два оператора:

    оператор dynamic_cast поддерживает преобразования типов во время выполнения, обеспечивая безопасную навигацию по иерархии клас- сов. Он позволяет трансформировать указатель на базовый класс в указатель на производный от него, а также преобразовать l-значение, ссылающееся на базовый класс, в ссылку на производный, но только в том случае, если это завершится успешно;

    оператор typeid позволяет получить фактический производный тип объекта, адресованного указателем или ссылкой.
    Однако для получения информации о типе производного класса операнд любого из операторов dynamic_cast или typeid должен иметь тип класса, в котором есть хотя бы одна виртуальная функция. Таким образом, опера- торы RTTI

    это события времени выполнения для классов с виртуальными функциями и события времени компиляции для всех остальных типов. В данном разделе мы более подробно познакомимся с их возможностями. Ис- пользование RTTI оказывается необходимым при реализации таких прило- жений, как отладчики или объектные базы данных, когда тип объектов, ко- торыми манипулирует программа, становится известен только во время вы- полнения путем исследования RTTI-информации, хранящейся вместе с ти- пами объектов. Однако лучше пользоваться статической системой типов
    C++, поскольку она безопаснее и эффективнее.
    11.1 Оператор dynamic_cast
    Оператор dynamic_cast можно применять для преобразования указа- теля, ссылающегося на объект типа класса в указатель на тип класса из той же иерархии. Его также используют для трансформации l-значения объекта типа класса в ссылку на тип класса из той же иерархии. Приведение типов с помощью оператора dynamic_cast
    , в отличие от других имеющихся в C++ способов, осуществляется во время выполнения программы. Если указатель или l-значение не могут быть преобразованы в целевой тип, то dynamic_cast завершается неудачно. В случае приведения типа указателя признаком неудачи служит возврат нулевого значения. Если же l-значение

    95 нельзя трансформировать в ссылочный тип, возбуждается исключение.
    Ниже мы приведем примеры неудачного выполнения этого оператора.
    Прежде чем перейти к более детальному рассмотрению dynamic_cast
    , посмотрим, зачем его нужно применять. Предположим, что в программе ис- пользуется библиотека классов для представления различных категорий служащих компании. Входящие в иерархию классы поддерживают функ- ции-члены для вычисления зарплаты: class employee { public: virtual int salary();
    }; class manager : public employee { public: int salary();
    }; class programmer : public employee { public: int salary();
    }; void company::payroll( employee *pe ) {
    // используется pe->salary()
    }
    В компании есть разные категории служащих. Параметром функции- члена payroll()
    класса company является указатель на объект employee
    , который может адресовать один из типов manager или programmer
    . По- скольку payroll()
    обращается к виртуальной функции-члену salary()
    , то вызывается подходящая замещающая функция, определенная в классе manager или programmer
    , в зависимости от того, какой объект адресован указателем.
    Допустим, класс employee перестал удовлетворять нашим потребно- стям, и мы хотим его модифицировать, добавив еще одну функцию-член bonus()
    , используемую совместно с salary()
    при расчете платежной ведо- мости. Для этого нужно включить новую функцию-член в классы, составля- ющие иерархию employee
    : class employee { public: virtual int salary(); virtual int bonus();
    }; class manager : public employee { public: int salary();
    };

    96 class programmer : public employee { public: int salary(); int bonus();
    }; void company::payroll( employee *pe ) {
    // eniieucoaony pe->salary() e pe->bonus()
    }
    Если параметр pe функции payroll()
    указывает на объект типа manager
    , то вызывается виртуальная функция-член bonus()
    из базового класса employee
    , поскольку в классе manager она не замещена. Если же pe указывает на объект типа programmer
    , то вызывается виртуальная функция- член bonus()
    из класса programmer
    После добавления новых виртуальных функций в иерархию классов при- дется перекомпилировать все функции-члены. Добавить bonus()
    можно, если у нас есть доступ к исходным текстам функций-членов в классах employee
    , manager и programmer
    . Однако если иерархия была получена от независимого поставщика, то не исключено, что в нашем распоряжении имеются только заголовочные файлы, описывающие интерфейс библиотеч- ных классов и объектные файлы с их реализацией, а исходные тексты функ- ций-членов недоступны. В таком случае перекомпиляция всей иерархии не- возможна.
    Если мы хотим расширить функциональность библиотеки классов, не добавляя новые виртуальные функции-члены, можно воспользоваться опе- ратором dynamic_cast
    Этот оператор применяется для получения указателя на производный класс, чтобы иметь возможность работать с теми его элементами, которые по-другому не доступны. Предположим, что мы расширяем библиотеку за счет добавления новой функции-члена bonus()
    в класс programmer
    . Ее объ- явление можно включить в определение programmer
    , находящееся в заголо- вочном файле, а саму функцию определить в одном из своих исходных фай- лов: class employee { public: virtual int salary();
    }; class manager : public employee { public: int salary();
    }; class programmer : public employee {

    97 public: int salary(); int bonus();
    };
    Напомним, что payroll()
    принимает в качестве параметра указатель на базовый класс employee
    . Мы можем применить оператор dynamic_cast для получения указателя на производный programmer и воспользоваться им для вызова функции-члена bonus()
    : void company::payroll( employee *pe )
    { programmer *pm = dynamic_cast< programmer* >( pe );
    // anee pe oeacuaaao ia iauaeo oeia programmer,
    // oi dynamic_cast auiieieony oniaoii e pm aoaao
    // oeacuaaou ia ia?aei iauaeoa programmer if ( pm ) {
    // eniieuciaaou pm aey auciaa programmer::bonus()
    }
    // anee pe ia oeacuaaao ia iauaeo oeia programmer,
    // oi dynamic_cast auiieieony iaoaa?ii
    // e pm aoaao niaa??aou 0 else {
    // eniieuciaaou ooieoee-?eaiu eeanna employee
    }
    }
    Оператор dynamic_cast< programmer* >( pe )
    приводит свой операнд pe к типу programmer*
    . Преобразование будет успешным, если pe ссыла- ется на объект типа programmer
    , и неудачным в противном случае: тогда результатом dynamic_cast будет 0.
    Таким образом, оператор dynamic_cast осуществляет сразу две опера- ции. Он проверяет, выполнимо ли запрошенное приведение, и если это так, выполняет его. Проверка производится во время работы программы. dynamic_cast безопаснее, чем другие операции приведения типов в C++, поскольку проверяет возможность корректного преобразования.
    Если в предыдущем примере pe действительно указывает на объект типа programmer
    , то операция dynamic_cast завершится успешно и pm будет инициализирован указателем на объект типа programmer
    . В противном слу- чае pm получит значение 0. Проверив значение pm
    , функция company::payroll()
    может узнать, указывает ли pm на объект programmer
    Если это так, то она вызывает функцию-член programmer::bonus()
    для вы- числения премии программисту. Если же dynamic_cast завершается не- удачно, то pe указывает на объект типа manager
    , а значит, необходимо при- менить более общий алгоритм расчета, не использующий новую функцию- член programmer::bonus()

    98
    Оператор dynamic_cast употребляется для безопасного приведения ука- зателя на базовый класс к указателю на производный. Такую операцию ча- сто называют понижающим приведением (
    downcasting
    ). Она применяется, когда необходимо воспользоваться особенностями производного класса, от- сутствующими в базовом. Манипулирование объектами производного класса с помощью указателей на базовый обычно происходит автоматиче- ски, с помощью виртуальных функций. Однако иногда использовать вирту- альные функции невозможно. В таких ситуациях dynamic_cast предлагает альтернативное решение, хотя этот механизм в большей степени подвержен ошибкам, чем виртуализация, и должен применяться с осторожностью.
    Одна из возможных ошибок – это работа с результатом dynamic_cast без предварительной проверки на 0: нулевой указатель нельзя использовать для адресации объекта класса. Например: void company::payroll( employee *pe )
    { programmer *pm = dynamic_cast< programmer* >( pe );
    // iioaioeaeuiay ioeaea: pm eniieucoaony aac i?iaa?ee cia?aiey static int variablePay = 0; variablePay += pm->bonus();
    // ...
    }
    Результат, возвращенный dynamic_cast
    , всегда следует проверять, прежде чем использовать в качестве указателя. Более правильное определе- ние функции company::payroll()
    могло бы выглядеть так: void company::payroll( employee *pe )
    {
    // auiieieou dynamic_cast e i?iaa?eou ?acoeuoao if ( programmer *pm = dynamic_cast< programmer* >( pe ) ) {
    // eniieuciaaou pm aey auciaa programmer::bonus()
    } else {
    // eniieuciaaou ooieoee-?eaiu eeanna employee
    }
    }
    Результат операции dynamic_cast используется для инициализации пе- ременной pm внутри условного выражения в инструкции if
    . Это возможно, так как объявления в условиях возвращают значения. Ветвь, соответствую- щая истинности условия, выполняется, если pm не равно нулю: мы знаем, что операция dynamic_cast завершилась успешно и pe указывает на объект programmer
    . В противном случае результатом объявления будет 0 и выпол- няется ветвь else
    . Поскольку теперь оператор и проверка его результата

    99 находятся в одной инструкции программы, то невозможно случайно вста- вить какой-либо код между выполнением dynamic_cast и проверкой, так что pm будет использоваться только тогда, когда содержит правильный ука- затель.
    В предыдущем примере операция dynamic_cast преобразует указатель на базовый класс в указатель на производный. Ее также можно применять для трансформации l-значения типа базового класса в ссылку на тип произ- водного. Синтаксис такого использования dynamic_cast следующий: dynamic_cast< Type& >( lval )
    Здесь
    Type&
    – это целевой тип преобразования, а lval
    – l-значение типа базового класса. Операнд lval успешно приводится к типу
    Type&
    только в том случае, когда lval действительно относится к объекту класса, для ко- торого один из производных имеет тип
    Type
    Поскольку нулевых ссылок не бывает, то проверить успешность выпол- нения операции путем сравнения результата (т.е. возвращенной оператором dynamic_cast ссылки) с нулем невозможно. Если вместо указателей ис- пользуются ссылки, тогда следующее условие: if ( programmer *pm = dynamic_cast< programmer* >( pe ) )
    Нельзя переписать в виде: if ( programmer &pm = dynamic_cast< programmer& >( pe ) )
    Для извещения об ошибке в случае приведения к ссылочному типу опе- ратор dynamic_cast возбуждает исключение. Следовательно, предыдущий пример можно записать так:
    #include < typeinfo> void company::payroll( employee &re )
    { try { programmer &rm = dynamic_cast< programmer & >( re );
    // eniieuciaaou rm aey auciaa programmer::bonus()
    } catch ( std::bad_cast ) {
    // eniieuciaaou ooieoee-?eaiu eeanna employee
    }
    }
    В случае неудачного завершения ссылочного варианта dynamic_cast возбуждается исключение типа bad_cast
    . Класс bad_cast определен в стандартной библиотеке; для ссылки на него необходимо включить в про- грамму заголовочный файл. (Исключения из стандартной библиотеки мы будем рассматривать в следующем разделе.)

    100
    Когда следует употреблять ссылочный вариант dynamic_cast вместо указательного? Это зависит только от желания программиста. При его ис- пользовании игнорировать ошибку приведения типа и работать с результа- том без проверки (как в указательном варианте) невозможно; с другой сто- роны, применение исключений увеличивает накладные расходы во время выполнения программы.
    11.2 Оператор typeid
    Второй оператор, входящий в состав RTTI, – это typeid
    , который позво- ляет выяснить фактический тип выражения. Если оно принадлежит типу класса и этот класс содержит хотя бы одну виртуальную функцию-член, то ответ может и не совпадать с типом самого выражения. Так, если выражение является ссылкой на базовый класс, то typeid сообщает тип производного класса объекта:
    #include < typeinfo> programmer pobj; employee &re = pobj;
    // n ooieoeae name() iu iiciaeiieiny a iia?acaaea, iinayuaiiii type_info
    // iia aica?auaao C-no?ieo "programmer" coiut <Операнд re оператора typeid имеет тип employee
    . Но так как re
    – это ссылка на тип класса с виртуальными функциями, то typeid говорит, что тип адресуемого объекта – programmer
    (а не employee
    , на который ссыла- ется re
    ). Программа, использующая такой оператор, должна включать заго- ловочный файл, что мы и сделали в этом примере.
    Где применяется typeid
    ? В сложных системах разработки, например при построении отладчиков, а также при использовании устойчивых объек- тов, извлеченных из базы данных. В таких системах необходимо знать фак- тический тип объекта, которым программа манипулирует с помощью указа- теля или ссылки на базовый класс, например для получения списка его свойств во время сеанса работы с отладчиком или для правильного сохране- ния или извлечения объекта из базы данных. Оператор typeid допустимо использовать с выражениями и именами любых типов. Например, его опе- рандами могут быть выражения встроенных типов и константы. Если опе- ранд не принадлежит к типу класса, то typeid просто возвращает его тип: int iobj; cout << typeid( iobj ).name() << endl; // ia?aoaaony: int

    101 cout << typeid( 8.16 ).name() <Если операнд имеет тип класса, в котором нет виртуальных функций, то typeid возвращает тип операнда, а не связанного с ним объекта: class Base { /* нет виртуальных функций */ }; class Derived : public Base { };
    Derived dobj;
    Base *pb = &dobj; cout <Операнд typeid имеет тип
    Base
    , т.е. тип выражения
    *pb
    . Поскольку в классе
    Base нет виртуальных функций, результатом typeid будет
    Base
    , хотя объект, на который указывает pb
    , имеет тип
    Derived
    Результаты, возвращенные оператором typeid
    , можно сравнивать.
    Например:
    #include employee *pe = new manager; employee& re = *pe; if ( typeid( pe ) == typeid( employee* ) )
    // ?oi-oi naaeaou
    /* if ( typeid( pe ) == typeid( manager* ) ) if ( typeid( pe ) == typeid( employee ) ) if ( typeid( pe ) == typeid( manager ) )
    */
    Условие в инструкции if сравнивает результаты применения typeid к операнду, являющемуся выражением, и к операнду, являющемуся именем типа. Обратите внимание, что сравнение возвращает истину: typeid( pe ) == typeid( employee* )
    Это удивит пользователей, привыкших писать:
    // вызов виртуальной функции pe->salary();
    Это приводит к вызову виртуальной функции salary()
    из производного класса manager
    . Поведение typeid(pe) не подчиняется данному меха- низму. Это связано с тем, что pe
    – указатель, а для получения типа произ- водного класса операндом typeid должен быть тип класса с виртуальными функциями. Выражение typeid(pe)
    возвращает тип pe
    , т.е. указатель на employee
    . Это значение совпадает со значением typeid(employee*)
    , тогда как все остальные сравнения дают ложь.
    Только при употреблении выражения
    *pe в качестве операнда typeid результат будет содержать тип объекта, на который указывает pe
    :

    102 typeid( *pe ) == typeid( manager ) // истинно typeid( *pe ) == typeid( employee ) // ложно
    В этих сравнениях
    *pe
    – выражение типа класса, который имеет вирту- альные функции, поэтому результатом применения typeid будет тип адре- суемого операндом объекта manager
    Такой оператор можно использовать и со ссылками: typeid( re ) == typeid( manager ) // истинно typeid( re ) == typeid( employee ) // ложно typeid( &re ) == typeid( employee* ) // истинно typeid( &re ) == typeid( manager* ) // ложно
    В первых двух сравнениях операнд re имеет тип класса с виртуальными функциями, поэтому результат применения typeid содержит тип объекта, на который ссылается re
    . В последних двух сравнениях операнд
    &re имеет тип указателя, следовательно, результатом будет тип самого операнда, т.е. employee*
    На самом деле оператор typeid возвращает объект класса типа type_info
    , который определен в заголовочном файле

    . Интер- фейс этого класса показывает, что можно делать с результатом, возвращен- ным typeid
    1   ...   5   6   7   8   9   10   11   12   ...   15


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