|
Программирование >> Оптимизация возвращаемого значения
Сравните это поведение с вызовом виртуальной функции. В такой ситуации всегда вызывается функция класса, наиболее близкого к динамическому типу объекта, для которого вызывается функция. Можно сказать, что виртуальные функции используют алгоритм наиболее подходящего , а исключения - алгоритм первого подходящего . Иногда компиляторы генерируют предупреждение, если оператор catch для производного класса следует за таким же оператором для базового класса (некоторые компиляторы генерируют сообщение об ошибке, потому что ранее в С++ подобный код считался некорректным), но опасность лучше предупреждать: избегайте описанной выше последовательности операторов catch. Например, предыдущий пример может быть переработан следующим образом: try { catch{invalid argument&ex) { Здесь обрабатываются ... исключения типа } invalid argument. catch{logic error&ex) { Здесь обрабатываются ... все другие исключения } THnalogic error. Таким образом, между передачей объекта функции в качестве параметра или использованием объекта для вызова виртуальной функции и генерацией объекта-исключения существуют три принципиальных отличия. Во-первых, для объектов-исключений всегда создаются копии; при перехвате по значению операция копирования повторяется дважды. Для объектов, передаваемых функции в качестве аргументов, операция копирования может не производиться вообще. Во-вторых, для объектов, используемых в качестве исключений, существует меньше операторов преобразования типов, чем для объектов, передаваемых функциям в качестве аргументов. Наконец, в-третьих, операторы catch обслуживаются в порядке их перечисления в исходном тексте программы, выполняется же первый из них, который может перехватить данное ис1слючение. При вызове виртуальной функции выбирается та из них, которая может обеспечить наилучшее совпадение с типом объекта, даже если она не первая по порядку в исходных текстах программы. Правило 13. Перехватывайте исключения, передаваемые по ссылке При создании оператора catch необходимо указать способ передачи объектов исключения данному оператору. Как и при указании способов передачи параметров функциям, здесь предусмотрено три варианта: по указателю, по значению или по ссылке. Рассмотрим сначала обработку исключения по указателю. Теоретически, это самый неэффективный способ реализации и без того медленного процесса передачи исключения из точки throw оператору catch (см. правило 15). Причина class exception { . . . } voidsomeFunction{) { static exception ex; throw&ex; voiddoSomething {) { try{ someFunction {) catch{exception*ex) { / / Из иерархии исключений стандартнойбиблиотеки С++ {см.правило 12) . / / Объект исключения. Сгенерировать в качестве исключения указатель на ex. / / Может генерировать исключения. Обнаруживает исключения; / / объект не копируется. Код выглядит чисто и аккуратно, но это впечатление обманчиво. Подобная программа будет работать, только если программист сможет, сгенерировав соответствующие указатели, обозначить объекты исключения таким образом, чтобы гарантировать их существование после того, как функции потеряют управление. Поскольку глобальные и статистические объекты работают прекрасно, программисты обычно легко забывают об этом ограничении и пишут примерно следующий код: void someFunction { exception ex throw&ex; Локальный объект исключения; он будет уничтожен, когда программа выйдет за область действия этой / / функции. Генерируется указатель / / на объект,который будет удален. Но это совершенно бесполезно, потому что при обработке такого исключения оператор catch получает указатель на объект, который больше не существует. В качестве альтернативы можно поместить указатель на новый динамический объект; voidsomeFunction{) { в том, что генерация исключения по указателю является единственным способом передачи информации об исключении, при котором не требуется копировать объект (см. правило 12). Приведем пример: thrownew exception; Установимуказатель на новый ... динамическийобъект (остается лишь надеяться, с1г } что новый оператор- см.правилоб -небудет cli генерировать исключения! ) Это поможет в будущем избежать проблемы Я только что обнаружил указа тель на уже уничтоженный объект , но сейчас необходимо решить: стоит ли уда лять полученный указатель? Если объект исключения был создан в куче, полу ченный указатель лучше удалить, чтобы не допустить утечки ресурсов. В против ном случае этот указатель следует оставить, иначе программа будет работать не предсказуемо. Возникает вопрос: что же делать? Четко ответить на него невозможно. Некоторые клиенты могли бы пропустит! адрес глобального или статического объекта, другие могли бы передать адрес ис- ключения в куче. Таким образом, перехват по указателю напоминает дилемму Гамлета: удалять или не удалять? Это вопрос, не имеющий однозначного ответа, поэтому лучше в него не углубляться. Более того, перехват исключения по указателю противоречит соглашениям, принятым в данном языке программирования. Все четыре стандартных исключения: bad alloc (генерируется, когда operator new (см. правило 8) не может удовлетворить запрос на выделение памяти), bad cast (генерируется при невозможности приведения типов посредством dynamic cast, см. правило 2), bad typeid (генерируется при применении dynamic cast к нулевому указателю и bad exception (генерируется при неожиданных исключениях, см. правило 14) - являются объектами, а не указателями на объекты. Поэтому их придется перехватывать по значению или по ссылке. Если программа работает со стандартными типами исключений, поиск по значению снимает вопрос об их удалении. Но поиск по значению требует, чтобы же объекты исключения копировались дважды при каждой генерации исключения Va (см. правило 12). При этом возникает проблема потери данных, которая заключа- но ется в том, что объекты исключения производного класса, обрабатываемые как исключения базового класса, теряют признаки своей производности . Такие от- ва слоенные объекты являются объектами базового класса: в них отсутствуют за объекты данных, определенные как элемент производного класса, и когда вирту- ш альные функции обращаются к ним, они сами распадаются на виртуальные функ- pi ции базового класса. (Абсолютно то же самое происходит, когда объект передается Д в функцию по значению.) Например, рассмотрим приложение, использующее иерархию классов исключений, которая расширяет стандартную: class exception { Как и в предыдущем примере , это public: стандартный класс исключений, virtual const char * what {) throw {) ; / / Выдает краткое описание исключения ... (см. в правиле 14 информацию
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |