Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 71
Текст из файла (страница 71)
Назовем такой класс ЬоЫег (держатель, хранитель), поскольку он предназначен для безопасного хранения объекта при выполнении различных вычислений. Ниже показано, как это можно сделать. // ройпсегз/)зо16ег.)зрр ееазр1аее <сурепке Т> с1азз Но1с)ег ( ргйчасе: 'Т* рпг; // Ссылка на хранимый объект рпЫ1с: // Конструктор по умолчанию Но1с)ег() : рсг(0) // Конструктор для указателя ехр11сйе Но1с)ег(Т* р) : рег(р) ( ) // Деструктор — освобождение объекта -Но1а () ( с)е1есе рег: ) // Присвоение нового указателя Но1йег<Т>й орегасог= (Т* р) ( с)е1есе рггу рег = ру гесигп *сЬйз; // Операторы для работы с указателем Тй орегаеог* (] сопев ( геспгп "ргг; ) Т* орегасог-> () сопзе ( гесигп рсг; ) // Получение указываемого объекта т* дев() сопев 20.1.
Но!бег и Тги!е 391 гесигп рег; // Освобождение от владения объектом чоЫ ге1еазе() ( ргг = О; // Обмен владением с другим хранителем чоЫ ехс)галде мйГЬ(НоЫег<Т>й )з) ( амар(ргг,)т.ргг)г // Обмен владением с другим указателем чо16 ехсЬапде м1с)г(Т*й р) ( виар(рсг,р); ) ргйчасе: // Копирование и копирующее присвоение запрещены Но1бег (Но1бег<Т> сопвгй) г Но16ег<Т>й орегасог= (Но16ег<Т сопзей)г ): Семантически наш класс получает во владение объект, на который указывает рсг. Этот объект должен быть создан с помощью оператора пем, так как при уничтожении объекта вьпывастся оператор с(е1есе .
Функция-член ге1еаве ( ) снимает обязанности г по управлению хранимым объектом с хранителя. Однако оператор присвоения достаточно интеллектуален для того, чтобы уничтожить и освободить ресурсы хранимого объекта ао избежание утечки памяти и прочих негативных последствий (так как оператор присвоения не возвращает указатель на ранее хранившийся объект).
Кроме того, в шаблон добавлены две функции-члена ехс)запде мйп)з(), которые позволяют нам обмениваться объектами без их уничтожения. Ниже пок,азш(о, как можно переписать наш пример при использовании шаблона НоЫег. чоЫ с)о гмо санди() ноЫег<яощегМпд> бйгзг (пем яошегМпд) ) ййгзг->регйогщ(); Но1бег<яотеСМпд> зесопй(пем Яощес)тйпд); зесопс(->регбогш(); ) 3 Для обеспечения гибкости можно добавить еще один параметр шаблона, который будет определять стратегию освобождения объекта. Глава 20.
Интеллектуальные указатели 392 Так гораздо понятнее. Благодаря работе деструктора Но1бег код стал не только безопасным в смысле исключений, нр теперь удаление объектов происходит автоматически и при обычном пути выполнения. Обратите внимание, что при инициализации нельзя использовать присваивающий синтаксис. НоЫег<ЯотеГЬТпд> Нгвг = пем яотеГЬТпд; // ОШИБКА Дело в том, что конструктор объявлен как ехр11с1г, и имеется определенное различие между Х х; У у(х); // Явное преобразование Х х; У у = х; // Неявное преобразование В первом случае новый объект типа у создается с использованием явного преобразования из типа Х, в то время как в последнем новый объект типа У создается с помощью неявного преобразования, которое в шаблоне Но1с)ег запрещено ключевым словом ехр11сйг.
20.1.3. НоЫег в качестве члена класса Избежать утечки ресурсов можно также, используя Но1с)ег внутри класса. Если член класса имеет этот тип, а не обычный тип указателя, нам зачастую не требуется иметь дело с этим членом в деструкторе, поскольку такой объект будет удален при удалении члена Но1с)ег. Кроме того, этот шаблон помогает избежать утечки ресурсов в случае генерации исключения при инициализации обьекта. Заметим, что деструкторы вызываются только для полностью сконструированных обьекгов и, если исключение генерируется в конструкторе, то деструкторы вызываются только для тех объектов-членов, конструкторы которых завершили свою работу без ошибок. Если не использовать шаблон Но1с)ег, это приведет к утечке ресурсов: например, если после первого успешного выделения ресурсов последовало неуспешное.
// ро1пгегв/гейаеза1.Ьрр с1авв КейМеюЬегв ( рг1чаге: Меттуре* рсг1/ Мезатуре* ркг2; риЬ11с: // Конструктор по умолчанию // — утечка при исключении во втором пеи пебмеюЬегв () .. рсг1(пеи Меттуре), ргг2(пеи Меютуре) ( ) 20.1. Но!бег и Тги!е 393 // Конструктор копирования // — утечка при исключении во втором пем КебМевЬегв (КейМевЬегв сопзсй х) рег1(пем МевТуре(*х.рсг1)) ргг2(пезг МевТуре(*х.рсг2)) ( // Оператор присвоения сопвс КебМевЬегвй орегасог= (КебмевЬегв сопвсй х) ( *ргг1 = *х.ргг1) *рсг2 = *х.ргг2; геспгп *сЬйвг -КебмевЬегв И ( г)е1еге ргг1; де1еге ргг2? ) //..
Если же вместо обычных указателей использовать шаблон Но16ег, то потенциальных утечек ресурсов можно легко избежать. // ройпгегв/ге1вепз2.Ьрр ()1пс1пг)е ")зо1с)ег.Ьрр" с1авв КеЕМевЬегв ( ргйчасе: Но1с)ег<Мевгуре> рсг1; но1с)ег<Мевгуре> рег2) риЬ11с: // Койструктор по умолчанию // — никаких возможных утечек КебМевЬегв () ргг1(пеы МевТуре), рсг2(пеы МевТуре) ( ) // Конструктор копирования // — никаких возможных утечек КебМевЬегв (КебМевЬегв сопвсй х) рсг1 (пеы МевТуре (*х.
рсг1) ) ргг2(пеы МевТуре(*х.рсг2)) ( ) // Оператор присвоения 394 Глава 20. Интеллектуальные указатели сопвс КегМешЬегвй орегагог= (КеЕМешЬегв сопвсй х) ( *рсг1 = *х.рсг1; *ргг2 = *х.ргг2; гесигп *гсвев; ) // Деструктор не является необходимым // (деструктор по умолчанию удаляет объекты рсг1 и рсг2) //... ): Заметим, что, хотя можно и не определять деструктор, все равно придется определить конструктор копирования и оператор присвоения.
20.1.4. Захват ресурса есть инициализация Общая идея, лежащая в основе класса Но1с)ег, — зозват ресурса есть инициализация (гезоигсе асов(з(((оп н (п)йайза6оп — кАп) — введена в [34). Вводя параметры шаблона для стратегии освобождения, соответствующий приведенной ниже схеме код чохе) с)о() ( // Захват ресурсов КЕЯ1ь гев1 = асс)ихге гевоигсе 1(); КЕЯ2* гев2 = асс1ихге гевоигсе 2(); // Освобождение ресурсов ге1еаве гевоигсе 2 (гев2); ге1еаве гевоигсе 1(гев1); ) можно заменить следующим: чоЫ с)о() ( // Захват ресурсов Но1бег<КЕЯ1,...> гев1(ассрзхге гевоигсе 1() ); Но1с)ег<КЕЯ2,...> гев2(асс)ихге гевоигсе 2()); 20.1.5.
Ограничения Но1йег Далеко не все проблемы решаются с помощью шаблона Но1с)ег. Рассмотрим пример. Яотегаепа* 1оас) вошесЬЕпд() ( 20.1. Но)бег и Тги1е 395 яотесЫпдь геви1г = пети яошесЫпдг геас) вогаегЫпд(гевп1с); гесигп геви1Г; В этом примере две вещи усложняют код. 1. В приведенной фунш(ии вызывается другая функция, геас) вомегЫпд (), которой в качестве аргумента должен передаваться обычный указатель. 2. 1оас) вогаегЫпд(] возвращает обычныйуказатель. При использовании шаблона Но16ег код становится безопасным в смысле исключений, но при этом повышается его сложность. Яогаес)тйпд* 1оаг) вотегЫпд() ( Но1с)ег<ЯогаеСЫпд> геви1Г (пеы ЯотеГЫпд) г геас) воагегЫпд(геви1с.дес ройпгег()) ЯошеаЫпд* гес = геви1с.дес ро1пгег() гевп1г .
ге1еаве ( ); геспгп ген: ) По-видимому, функция геас) вогаегМпд ( ) не осведомлена о существовании шаблона Но1с1ег, так что мы должны получить реальный указатель с помощью вызова функции- члена дег ройпгег ( ) . При использовании данной функции Но1с)ег продолжает осуществлять управление объектом, и получатель результата вызова функции должен отдавать себе отчет, что владельцем объекта, на который указывает данный указатель, является не он, а Но1с)ег. Если функции дес ро1пг.ег () в шаблоне нет, можно воспользоваться пользовательским Оператором разыменования * вкупе со встроенным оператором взятия адреса й.
Еще одной альтернативой является явный вызов оператора ->. геаг) вогаегЫпд (а*геви1г); геад вогаегЫпд(гевп1г.орегагог->()) Вы наверняка согласитесь, что второй вариант — очень жалкая альтернатива первому Однако такой вариант не оставит потенциальную опасность подобных действий незамеченной программистом. Еще одно замечание по приведенному ранее примеру кода касается вызова функции- члена ге1еаве ( ) для снятия владения с обьекта. Это предотвращает автоматическое уничтожение объекта при завершении функции, и его можно вернуп.
вызывающей функции Обратите внимание на необходимость сохранения возвращаемого значения во временной переменной перед освобожлением. Глава 20. Интеллектуальные указатели 396 яоазес)ттпд* гег = геви1т.дес ро1птег() геви1с.ге1еаве(); гетигп тес: Чтобы избежать этого, можно использовать инструкцию вида гетитп геви1т.ге1еаве(); Однако при этом следует изменить функцию ге1еаве () с тем, чтобы она возвращала указатель на освобождаемый объекг. севр1ате< турепагве т> с1авв Но1с)ег ( т* ге1еаве() т* гет = рог; рт = О; гесигп гет: Из всего этого можно сделать вывод: интеллектуальные указатели не так уж интеллектуальны, но их использование способно намного упростить жизнь программиста.
20.1.6. Копирование НоЫег Вы, вероятно, заметили, что в реализации шаблона Но1с)ег мы запретили копирование, поместив конструктор копирования и копирующий оператор присвоения в закрытой части класса. Ведь в чем состоит цель копирования, как не в получении второго объекта, по сути идентичного первому? В случае интеллектуального указателя Но1с)ег это означает, что копия также является владельцем объекта, и попытка двух освобождений объекта со стороны обоих интеллектуальных указателей неизбежно приведет к хаосу и некорректной работе программы.