Язык программирования Си++ (1119468), страница 10
Текст из файла (страница 10)
*/ throw Except (67); }catch (const Except Err) // Обработка исключительной ситуации{ cerr << “Exception (” << Err.i << “)\n”; }Исключительная ситуация считается обработанной в самый начальныймомент входа в обработчикЛюбые исключения, возникшие во время выполнения обработчика,обрабатываются обработчиками объемлющих блоков, циклов обработки невозникает:class Overflow {/* ... */ };void f () {try {/* ...
*//* ... */ throw Overflow (); }catch (Overflow) {/* ... */ throw Overflow (); }// Обработка исключения вне функции f ()}199Стандартные исключения• В языке Си++ имеются стандартные исключительные ситуации,генерируемые при выполнении тех или иных операций:ИмяГенерирующая операцияЗаголовочныйфайлbad_allocnew<new>bad_castdynamic_cast<typeinfo>bad_typeidtypeid<typeinfo>bad_exceptionспецификация исключения<exception>out_of_rangeat (); bitset<>::operator []()<stdexcept>invalid_argumentконструктор bitset<stdexcept>overflow_errorbitset<>::to_ulong ()<stdexcept>ins_base::failure ins_base::clear ()<ios>200Динамическое преобразование• Бинарная операция языка динамического преобразования типавозвращает правильный указатель (или ссылку) в случаеправильного предположения о типе объекта:dynamic_cast <T *> (p)dynamic_cast <T &> (r)• Операция имеет смысл только при работе с виртуальнымифункциями и указателями на объекты:class student{ /* ...
*/ }; // полиморфный классclass student2c : public student { /* ... */ }; // производный классint main (){ student s, * ps; student2c ds, * pds;pds = & ds; // derived * = derived *ps = pds; // base * = derived *pds = ps; // опасно, но возможно после предыдущего присваивания201}Динамическое преобразование• Обычно динамическое преобразование типауказателя проводится непосредственно в условномоператоре:void f (student * ps) { // ps указывает на некоторый классstudent2c * pds;if ((pds = dynamic_cast<student2c*> (ps)) != 0){ pds -> print ();}else{ // реакция на получение объекта другого типа}}202Динамическое преобразование• Результат операции динамического приведения типауказателя dynamic_cast <T*> (p) эквивалентенприведению типа указателя p к типу указателя T*• Динамическое приведение типа не допускаетнарушений правил доступа к закрытым изащищённым базовым классам• Операция возвращает нулевое значение, еслиобнаруживается:1.
Передача в качестве исходного значенияпреобразования нулевого операнда (p == 0)2. Неоднозначность при поиске базового класса типа TДинамическое преобразование• Параметр операции должен быть ссылкой илиуказателем на полиморфный тип• Результирующий тип не обязан бытьполиморфным (исходный объект при этомвсё равно должен быть полиморфным):student * ps; student2c * pd; int * pi;void * pv1 = dynamic_cast<void *> (ps); // правильноvoid * pv2 = dynamic_cast<void *> (pd); // правильноvoid * pv3 = dynamic_cast<void *> (pi); // ОШИБКА204Статическое преобразование• Статическое приведение типа не анализирует объект,который оно приводит• Статическое приведение типа формирует новыйуказатель нужного типа:student2c * g (void * p) {student * ps = static_cast<student*> (p);// ps указывает на неизвестный классreturn dynamic_cast<student2c*> (ps);// результат не может быть предсказан}205Статическое преобразование• Ограничения, наложенные на возможностистатического приведения типов, позволяютпроводить такое преобразование только для:(1) родственных типов из одной иерархииклассов (проверка родства классовотсутствует лишь при преобразованииуказателя из типа void*)(2) арифметических типов (даже не оченьблизких), например,float => int,int => enum206Преобразование типов ссылок• Динамическое приведение ссылок имеет особенности,отличающие его от приведения указателей• Приведение указателя к новому типу может привести кполучению нулевого значения, означающего, что полученныйуказатель не указывает ни на какой объект• Динамическое приведение указателя p к новому типу Tdynamic_cast<T*> (p) отвечает на вопрос: “Может лиуказатель p указывать на объекты типа T?”• Ссылка всегда связана с некоторым объектом, нулевых ссылокв программах быть не может• Для ссылки r операция динамического приведения типаdynamic_cast<T&> (r) является утверждением: “Ссылка rможет быть связана с объектом типа T!”207Фундаментальные отличиямежду указателями и ссылками• Если операция динамического приведения ссылки непринадлежит ожидаемому типу, возбуждаетсястандартная исключительная ситуация bad_cast:void f (student * p, student & r){ student2c *pp; student2c & pr;if (pp = dynamic_cast<student2c*> (p)){ /* использование указателя pp */ }else { /* указатель pp не имеет нужного типа */ }pr = dynamic_cast<student2c&> (r);/* использование ссылки pr */}208Фундаментальные отличиямежду указателями и ссылками• Для защиты от неудачных приведений к ссылкенеобходимо организовывать перехват стандартногоисключения:void g (){ try { f (new student2c, * new student2c);// нормальный вызов с нужным типомf (new student5c, * new student5c);// неверный тип, выход в перехватчик}catch (bad_cast) { /* обработка исключения */ }}209Операция определения типа• Унарная операция typeid, имеющая смысл дляуказателей и ссылок на объекты полиморфных типов,позволяет точно определить тип исследуемого объекта• Если операнд нужного типа имеет значение 0, операциявозбуждает исключительную ситуацию bad_typeid• Результатом операции является ссылка на классстандартной библиотеки по имени type_info• Библиотечными средствами гарантируется, что в этомклассе обязательно определены операции сравнения наравенство и на неравенство, а также метод, выдающийуказатель на строку, содержащую символьноепредставление имени типа объекта (зависящее отреализации)210Операция определения типа• Предполагается, что операция typeid должна использоватьсяпримерно в таком контексте:class type_info // это определение есть часть библиотеки!{ /* ...
*/ public: bool operator== (const type_info&) const;public: bool operator!= (const type_info&) const;const char * name () const; // выдаёт имя типа};if (typeid (* pb) == typeid (D)){ /* … */pd = dynamic_cast<D*> (pb); // на 0 можно не проверятьcout << typeid (* pb).name () << “, ” << typeid (* pd).name ();// Будет напечатано, например://class student, class student2c} // Показанные преобразования лучше, чем pd = (D*) pbВиды преобразования типов• Кроме динамического и статического преобразований типов,имеются ещё два вида преобразования: небезопасная операция const_cast отменяетконстантность или произвольную изменяемость объекта:const int * q = /* ...
*/; int * p = const_cast<int *> (q); ещё более опасная операция reinterpret_castприсваивает указателю значение, относящееся к другойиерархии наследования, то есть может преобразовыватьнесвязанные указатели (или числа в указатели):int t = 0xf0;p = (int *) t;// так пишут на языке Сиp = reinterpret_cast<int *> (t); // так пишут на языке Си++212Виды преобразования типов• Все преобразования типов, унаследованные от языка Си,могут в Си++ выражаться с помощью некоторыхкомбинаций операций static_cast, const_cast иreinterpret_cast• Этот стиль преобразований более точно указываетразличия между исходным и результирующим типами• В преобразовании вида (T) expr невозможно предсказать,какое именно преобразование выполняется, поэтому отнего следует отказываться, выбирая одно изпреобразований, характерных для Си++• Наиболее безопасным является преобразованиеполиморфных типов dynamic_cast с динамической213проверкой во время выполнения программыОбобщённое программирование• Концепция обобщённого программированияподразумевает использование типов в качествепараметров определений классов и функций• Механизм шаблонов позволяет описыватьпроизвольные обобщения множества алгоритмов• Шаблон класса определяет данные и операциипотенциально неограниченного множествародственных классов, а шаблон функции определяетнеограниченное множество родственных функций• Особенно полезно использование шаблонов припроектировании и реализации стандартной214библиотекиОбобщённое программирование• Инстанцирование – это процесс формированияописания класса или функции по шаблону и егофактическим параметрам• Получаемое для конкретного значения фактическихпараметров шаблона описание типа называетсяспециализацией шаблона• Сформированные классы или функции становятсясамыми обычными классами и функциями, которыеподчиняются всем правилам языка для классов ифункций215Обобщённое программирование• Шаблон может иметь несколько параметров (неменее одного),• Стандарт языка Си++ допускает параметры шаблонов: ти́ повые (typename, class) параметры, которые сами являются шаблонами интегральные типы: знаковые и беззнаковые целые типы,bool, char, wchar_t перечислимые типы (не относятся к интегральным,но их значения приводятся к интегральным в результатецелочисленного расширения) указатели на объекты, функции или на члены классов ссылки на объекты или функции216Обобщённое программирование• Параметр шаблона не может иметь тип void, не может бытьобъектом пользовательского типа или иметь плавающий тип:template <double* d> class X; // OKtemplate <double& d> class X; // OKtemplate <double d> class X; // ОШИБКА• Фактические параметры, соответствующие формальнымпараметрам интегральных и перечислимых типов, должныбыть константами• В теле шаблона все параметры, не являющиеся типамирассматриваются как константы• Параметры шаблонов могут использоваться также и дляопределения следующих параметров этих же шаблонов:template <class T, T par2_name> class C { /* … */ }217Шаблоны функций• Шаблон функции объявляется следующим образом:template <список_формальных_параметров_шаблона>возвращаемый_типимя_функции (список_формальных_параметров_функции){ /*...*/ }• Примером может служить шаблон функции вычислениястепени целого числа, имеющий в качестве параметразначение целого типа:template<int n> inline int power (int m){ int k = 1;for (int i = 0; i < n; ++ i) k *= m;return k;}218Шаблоны функций• При обращении к функции-шаблону после именифункции в угловых скобках указываются фактическиепараметры шаблона – имена реальных типов илизначения объектов, и лишь затем обычныефактические параметры функции:имя_функции<список_фактических_параметров_шаблона>(список_фактических_параметров_функции)• Пример обращения к функции-шаблону спараметром:int main() { int m = 4; cout << power<3> (m) << endl; }219Шаблоны функций• Имея обычную функцию, можно определить для неё шаблон,который позволит использовать данные различных типов:int max (int x, int y) { return x > y ? x : y; }template<class T> T max (T x, T y) { return x > y ? x : y; }• При вызове функции автоматически определяется, какаяверсия шаблона будет использована, то есть фактическиепараметры шаблона выводятся из фактических параметровфункции• При вызовеvoid f () { /*…*/ max (1, 2); /*…*/ }формируется вариант функции int max (int, int)// T ≡ int• При вызовеvoid f () { /*…*/ max (‘a’, ‘a’); /*…*/ } // T ≡ charформируется второй вариант функции char max (char, char)220Шаблоны функций• Параметры могут иметь разные типы:void f () { /* ...