Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 93
Текст из файла (страница 93)
Управление ресурсами Теперь, как и для локальных объектов, реализация С++ берет на себя все заботы о деталях выполнения процесса (никаких дополнительных действий от программиста не требуется). Например, если исключение возникнет после того, как прошла инициализация аа, но до того, как создан ЬЬ, будет автоматически вызван деструктор для па (но не для ЬЬ). Итак, где годится рассмотренная простая модель получения ресурсов, от программиста не требуется писать явный код обработки исключений.
Самым распространенным ресурсом является память. Например: с1азз У ( 1пг* р; гоЫ 1п11() ) риЫс: у(1пг 5) ( 1» = пемУ Й1[к); ииг (); ) -У() (ае1еге [] р; ) У... )' Такая практика весьма распространена и она может приводить к «утечкам памяти» (тепюгу [еа)сз). Если в 1пй() будет сгенерировано исключение, то выделенная в конструкторе память не освобождается — деструктор не будет вызван, ибо объект сконструирован к этому моменту не полностью. Вот более безопасный вариант: с1 г ( ° кесто <ии> р) гоЫ (пй (); риЫс: с(1пзз): р(з) ( ииг(); ) УУ ... Память, используемая вектором р, теперь находится под управлением типа гесгог.
если 1п[1() сгенерирует исключение, выделенная память будет (неявно) освобождена вызовом деструктора для р. 14.4.2. АМо р1г Стандартная библиотека предоставляет классовый шаблон аиго ргг, поддерживаюший технику «получение ресурса есть инициализация». В своей основе аиго рзг инициализируется указателем и может быть разыменован аналогично указателю.
Кроме того, указуемый объект неявно удаляется (уничтожается) тогда, когда объект типа аиго ргг выходит из области видимости. Например: коЫу" (Роги( р1, Ротгр2, пиго ргг<С1гс[е> рс, Ялпре* рЬ) ( аиго рсг<Яьаре> р (лен Йес(алд1е (р1, р2) ); УУ р указыеаегл на гес(аля(е ашо рзг<БЬпре> рЬок (рЬ) ) р->го(иге (45) г УУ используем аиго РО <5Ьаре> как Кларе « и Глава ) 4 Обработка исключений 448 (1(<п а тезз) гаго<с Мезе() // ... ) //вспомогательный класс <етр<а<е <с!азз У> з»ис< пи<о фг ге~( /*...*/ ) з <етр<а<е<сызз Х> сйиз зЫ:: аи<о р» ( Х.
рй< риЬВс: <ура<Ге!' Х е1етел< бя<е; р внимание: копирующий конструктор и присваивания имеют не-сопл< аргументы. елрйсиаи<о рй (Х* р=О) йюи () (рй=р! ) //<лгои<)-"ничего не генерировать"; смб!4б пи<о рй (аи<ор»ь а) Йюи () 1 Р копируем, затем аро — — 0 1етрьие<с<азз у> аи<о р<т(аи<о )из<у>а а) <ьплт() ! ркопируем, затем ар<г=О -аи<о р<т ( ) йюю() ( <Ге<е<е р<г; аи<о р~га орега<ог=(аи<о р<та а) <Ьго<т(); Р копируем и а.рй — — О <етр<а<е<с<азз у> асио р»а орега<ою (аи<о рй < у>ь а) йю<т (); ркопируем и ар<г=0 <етрйие<сйзм у> аи<о р»ь орепиог= (аи<о р<т ю1< у>ь а) <Ьго<т () 1 р копируем и ге!еаза Ха арета<от* () сопл< <йю<е() ( гейил "рй'! ) Х" оретай»->() соля«йю<г() ( ге<иглрй'1 ) Х* Ое< О солт< йго<т О ( таил рй ! ) р извлечь указатель Х" ге1еазе() <Ью<т() ( Х" ~р»< рй=О! гей<гп<! ) //передачавладения тоЫ теле<(Х* р =О) <Ью<ты ((У(р! =р») (<(в<егер»! рй=р; ) ) Ркопирование из аи<о рй- ге( //копирование в аи<о ро ге) рдеструктив, коп-е из аи<о ро аи<о рй (аи<о р<г ге(<Х>) <Ью<т () 1 Гетрйие<с<азз У> арета!оган<о рй ге!<У>() йюн () < Гетрй<е<сйзз У> орепиог аи<о рй <У> () йгои () ! )< Цель ай<о р<т те(' — реализовать семантику деструктивного копирования для аи<ор<т, делая невозможным копирование сопл< ай<ар».
Если 22* может быть преобразован в В*, тогда конструктор и операция присваивания шаблона могут (явно или неявно) преобразовывать аи<о рй <21> в ай<о р<т<В>. Например: юЫО(Сйс!е* рс) ( аи<о р»<С»с<е> р2 (рс) ( //теперь р2 отвечает за удаление ли<о рй <С»с1е> рЗ (р2) 1 // теперь рЗ отвечает за удаление (а р2 нет) Здесь объекты типа Яес<апя1е, Ягоре (адресуется через рЬ) и С!тс1е (адресуется с помощью рс) корректно уничтожаются независимо от того, генерируется исключение или же не генерируется.
Для достижения семантики владения (октегзй!р летал<к<), копирование объектов типа ай<о рй раднкаяьно отлИчается от копИроВаНИя Обычных указателей (реализуется семантика деструктивного копирования — <(ел<гас<!ие сору зетап<1сз): если некоторый объект типа ай<о рй' копируется в другой объект того же типа, то исходный объект после этого уже ни на что не указывает. Из-за того, что копирование объектов типа аи<о р<т изменяет их самих, нельзя скопировать объекты типа сои<< аи<о рй'.
Шаблон аи<ор<т определен в файле <тетогу>: 449 ) 4 4. Управление ресурсами ,о2->зп = 7; зу ошибка программиста: р2.яе(О ==0 Хлоре* рз = рЗ.еег О; //извлекаем указатель из ашо ргг виго рзг<ЯЬаре> арз (рЗ) гч' передаем владение и преобразуем тип виго ро<Сзгс1е> р4 (рс); 77 ошибка программиста: теперь и р4 отвечает за удаление ) Эффект принадлежности объекта двум виго ргг не определен; скорее всего, зто приведет к двукратному уничтожению объекта (с отрицательными последствиями). Отметим, что из-за семантики деструктивного копирования объекты типа виго рзг не удовлетворяют требованиям для элементов стандартных контейнеров и стандартных алгоритмов, таких как заиО .
Например: иесгог<аигоргг<5Ьаре»ь у; дола<но: использование аиго р(г в контейнере (1 ... логе[у.бед(п (), и.епд() ); 77не делайте этого: сортировка может испортить ч Ясно, что аиго ргг не является интеллектуальным указателем (зшап рошгег); он лишь без дополнительных накладных расходов реализует сервис, для которого и был задуман — повышенную надежность (безопасность) кода в связи с возможностью исключений. 14.4.3. Предостережение Не все программы должны обладать повышенной живучестью по отношению к любым видам ошибочных ситуаций, и не все ресурсы столь критичны, чтобы оправдать совокупные усилия для применения принципа «получение ресурса есть инициализация», типа виго ри и механизма сагой (... ) .
Например, для большинства программ, которые просто читают ввод и далее работают в направлении своего завершения, наиболее естественной реакцией на возникновение серьезной ошибки будет прекращение работы (после выдачи приемлемого диагностического сообщения). Это позволяет системе освободить ранее выделенные ресурсы, а пользователю — запустить программу заново и ввести корректные данные. Рассмотренная же стратегия предназначена для приложений, которые не могут просто так завершить работу. В частности, разработчик библиотеки не может знать требований программы, использующей библиотеку, в отношении стратегии поведения в случае возникновения ошибочных ситуаций.
В результате, он вынужден избегать любых форм безусловного прекращения работы и вынужден заботиться об освобождении ранее вьщеленных ресурсов перед возвратом в головную программу. Для таких вот случаев и подходит стратегия «получение ресурса есть инициализация» в совокупности с исключениями для сигнализации об ошибочном ходе выполнения библиотечных функций.
14.4.4. Исключения и операция пев)г Рассмотрим пример: уоЫЗ'(Агепаь а, Х* Ьифег) ( Х* р1 = пезгХ; Х* р2 = пет Х [10]; Глава 14. Обработка исключений 450 Х* рЗ = пв»г(ьбиЯег [10] ) Х; ((поместить Хв Ььфег (освобождение не нужно) Х* р4 = нет(ьбиЯег[11] ) Х[10]; У выделить из Агепо а (освободить из Агепо и) Х» рз = пвн (а) Х; Х* рб = пвн (в) Х [10] ) Что произойдет, если конструктор класса Хсгенерирует исключение? Освобождается ли память, выделенная функцией орегагог ие)гО? В обычном случае ответ положителен, так что инициализации р1 и р2 не вызывают утечки памяти. Когда же используется синтаксис размещения 510.4.11), ответ не так прост; некоторые варианты применения этого синтаксиса включают выделение памяти, подлежащей освобождению, а другие не включают.
Более того, главной целью синтаксиса размещения является возможность нестандартного выделения памяти, так что и ее освобождение должно быть нестандартным. Следовательно, необходимые действия зависят от используемой схемы вьшеления памяти: если для выделения памяти вызывалась функция У::орегагог ие»в(), то требуется вызвать функцию У:: орегагог бе(еге () (еслн она определена), а в противном случае память освобождать не нужно.
Массивы обрабатываются аналогичным образом (95.6.1). Данная стратегия работает как с операцией «размещающее иев» из стандартной библиотеки 610.4.11), так н в случае, когда пользователь сам реализовал функции для вьшеления/освобождения памяти. 14.4.5. Исчерпание ресурсов То и дело возникает вопрос, что делать, когда попытка получить ресурс завершилась неудачно? Например, мы можем с легкой душой открывать файл (используя функциюуорвп () ) или запрашивать блок свободной памяти (с помощью операции пеи), даже не побеспокоившнсь о том, что будет, если файла нет в указанном месте или если нет достаточного объема свободной памяти. Сталкиваясь с такими проблемами, программист может выбрать одну из двух стратегий реагирования: ° Возобновление (гезитрбоп): попросить вызвавшую функцию справиться с проблемой и продолжить работу.
° Завершение ((егт!пабоп): бросить подзадачу и вернуть управление вызвавшей функции. В рамках первой стратегии вызвавшая функция должна быль готова помочь решить проблему с выделением ресурса (неизвестным участком кода), а во втором случае — она должна быть готова отказаться от затребованного ресурса. Второй вариант в большинстве случаев намного проще н позволяет лучше реализовать разделение уровней абстракции.
Обратите внимание, что при этом речь идет не о прекращении работы программы, а лишь о прекращении частной подзадачи. Стратегия завершения означает стратегию возврата из места, где некоторые вычисления окончились неудачей, в обработчик ошибок, связанный с вызвавшей функцией (может, например, повторно запустить неудавшиеся ранее вычисления), а не попытку устранения проблемы н возобновление вычислений с места, где проблема была обнаружена. В языке С++ стратегия возобновления поддерживается механизмом вызова функций, а стратегия завершения — механизмом обработки исключений. Обе стра- 451 14.4. Управление ресурсами тегии можно проиллюстрировать простым примером реализации и использования функции орега(ог пет(): юЫ* орегагог пел (иге г эае) ( уог(;; ) ( (Г(го!й* р = тайас (иге) ) ге!лги р; // пытаемся найти память (Г( яеэг Ьапйег == О) гдгольЬай а!!осы; //нет обработчика: сдаемся иен Ьаийег(); // запрашиваем помощь ) ) юЫ ту пеьг Ьапйег() ( т! по оу Ьугеэ Гоилй =Ялй готе тетогу(); (!(ло о/" Ьугев Гелий < тш ат)осапоп) гвгоэг Бай айос(); ) // сдаемся Где-то должен быть соответствующий ггу-блок: пу //...