|
Программирование >> Оптимизация возвращаемого значения
Можно было бы предположить, что передача исключения от точки throw в точку catch происходит по механизму, в общих чертах совпадающему с механизмом передачи аргумента функции из точки ее вызова в ее тело. Однако есть и существенные отличия. Начнем со сходства. И аргументы функций, и исключения можно передавать тремя способами: по значению, по ссылке и при помощи указателя. Но последствия этих вариантов передачи различаются довольно серьезно. Причина в том, что после вызова функции управление в конечном итоге вернется в вызывающий модуль (по крайней мере, если в функции нет ошибок), но при генерации исключения управление не возвращается в модуль, содержащий оператор throw. Рассмотрим функцию, которая и передает тип Widget в качестве параметра, и генерирует его как исключение: / / Функция читает значение Widget из потока. istreamoperator>>{istream&s, Widget&w) ; voidpassAndThrowWidget() WidgetlocalWidget; cin >>localWidget; ПередаетlocalWidget в качестве параметра оператору >>. throwlocalWidget; ГенерируетlocalWidget / / как исключение. Когда объект localWidget передается в operator , сам localWidget не копируется. Вместо этого ссылка w в теле operator>> оказывается связанной с объектом localWidget, и все, что происходит со ссылкой w, на самом деле происходит с объектом localWidget. Если же localWidget генерируется в качестве исключения, то события развиваются совсем иначе. Независимо от того, перехватывается ли исключение по ссылке или по значению (оно не может перехватываться по указателю - это привело бы к ошибке несоответствия типов), создается копия localWidget, которая передается в качестве аргумента блоку catch. Так и должно быть, потому что localWidget покинет область видимости, как только управление покинет функцию passAndThrowWidget, и тогда будет вызван деструктор localWidget. Если бы в блок catch передавался сам localWidget, то catch получал бы полуразрушенный объект типа Widget или нечто, что было когда-то объектом типа Widget. Это было бы бесполезно, и спецификация С++ определяет, что объект, генерируемый в качестве исключения, всегда копируется. Такое копирование происходит даже тогда, когда нет опасности, что объект может быть уничтожен. Например, если объект localWidget в функции passAndThrowWidget объявлен с атрибутом static: voidpassAndThrowWidget{) { static Widget localWidget; Теперь это статический объект, онбудет cin >>localWidget ; throwlocalWidget ; существовать до конца программы. / / Данная часть работает по-прежнему. / / По-прежнему создается копияlocalWidget. ДЛЯ генерации исключения по-прелснему будет использована копия объекта localWidget. Это означает, что даже если исключение будет перехвачено по ссылке, блок catch сможет изменить не сам объект localWidget, а только его копию. Такое обязательное копирование объектов-исключений поможет вам понять и другое отличие между передачей параметров и генерацией исключений: последнее обычно выполняется намного медленнее (см. правило 15). Когда объект копируется для использования в качестве исключения, данная операция производится с помощью конструктора копирования. Это - единственный конструктор в классе, соответствующий не динамическому, а статическому типу объекта. Посмотрим, например, на слегка измененную версию функции passAndThrowWidget: classWidget{...}; class SpecialWidget: public Widget{...); voidpassAndThrowWidget{) SpecialWidgetlocalSpecialWidget; Widgets rw =localSpecialWidget; throw rw; / / rw - это ссылка на объектSpecialWidget. Генерируется / / исключение типа Widget! В этом примере исключение имеет тип widget, хотя rw является ссылкой на SpecialWidget. Так происходит потому, что статический тип rw равен Widget, а не SpecailWidget. В действительности же rw ссылается на SpecialWidget, однако ваши компиляторы данный факт не воспринимают, они видят только статический тип rw. Возможно, такое поведение не совпадает с желаемым, но оно согласуется со всеми другими случаями копирования объектов в С++. Копирование всегда основано на статическом типе объекта (кроме метода, описанного в правиле 25 и позволяющего осуществлять копирование на основе динамического типа объекта). То обстоятельство, что исключения являются копиями других объектов, влияет на их распространение за пределы блоков catch. Посмотрите на эти два блока catch, которые на первый взгляд выполняют одни и те же операции: catch (Widget& w) { / / Перехватываем исключение типа Widget . / / Обрабатываем исключение . ... Обрабатываемисключение. throw W; / / Генерируем копию перехваченного / / исключения для последующей обработки. Как видите, первый блок повторно генерирует перехваченное исключение, а второй - создает его новую копию. Очевидно, что во втором варианте дополнительная операция копирования снижает производительность программы. Но существуют ли другие различия между этими двумя подходами? Да, существуют. Первый блок повторно генерирует перехваченное исключение независимо от его типа. В частности, если было перехвачено исключение типа SpecialWidget, то первый блок повторно сгенерирует исключение типа SpecialWidget, хотя статический тип w и равен Widget. Это происходит потому, что при повторной генерации копия не создается. Второй блок catch генерирует новое исключение, которое всегда будет иметь тип Widget из-за статического типа W. Вообще говоря, желательно использовать throw; для повторной генерации исключения, чтобы гарантировать неизменность типа. Кроме того, это оптимальнее с точки зрения эффективности, потому что программе не приходится создавать новый объект-исключение. (Кстати, копия исключения является временным объектом. Как показано в правиле 19, это позволяет компиляторам при оптимизации генерируемого кода уклоняться от создания копий. Тем не менее, не стоит всецело полагаться на компиляторы. Исключения используются довольно редко, поэтому маловероятно, что производители компиляторов будут прикладывать много усилий для оптимизации обработки исключений). Рассмотрим три типа аргументов catch, с помощью которых можно перехватить исключение Widget, сгенерированное функцией passAndThrowWidget: catch {Widget w) ... Перехват исключения по значению. catch (Widget&w) ... Перехват исключения по ссылке, catch(const Widget&w) ... Перехватисключения по ссылке на const. Этот фрагмент демонстрирует еще одно различие между передачей параметров и распространением исключений. Генерируемый объект-исключение (который, как пояснено выше, всегда является временным) может быть перехвачен по простой ссылке, ссылку на const можно не использовать. Передача временного объекта по ссылке без атрибута const запрещена при вызове функции (см. правило 19) и разрешена для исключений. Не будем пока обращать внимание на это различие и вернемся к копированию объектов-исключений. Известно, что при передаче функции аргумента по throw; Повторно генерируем исключение для последующей обработки. catch(Widget&w) Перехватываемисключение типа Widget.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |