Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

389

8.4.2. Шаблон auto_ptr А

В стандартной библиотеке С++ auto_ptr является шаблоном класса, призванным помочь программистам в манипулировании объектами, которые создаются посредством оператора new. (К сожалению, подобного шаблона для манипулирования динамическими массивами нет. Использовать auto_ptr для создания массивов нельзя, это приведет к непредсказуемым результатам.)

Объект auto_ptr инициализируется адресом динамического объекта, созданного с помощью оператора new. Такой объект автоматически уничтожается, когда заканчивается время жизни auto_ptr. В этом подразделе мы расскажем, как ассоциировать auto_ptr с динамически размещаемыми объектами.

Для использования шаблона класса auto_ptr необходимо включить заголовочный файл:

#include <memory>

auto_ptr< type_pointed_to > identifier( ptr_allocated_by_new ); auto_ptr< type_pointed_to > identifier( auto_ptr_of_same_type );

Определение объекта auto_ptr имеет три формы: auto_ptr< type_pointed_to > identifier;

Здесь type_pointed_to представляет собой тип нужного объекта. Рассмотрим последовательно каждое из этих определений. Как правило, мы хотим непосредственно инициализировать объект auto_ptr адресом объекта, созданного с помощью оператора new. Это можно сделать следующим образом:

auto_ptr< int > pi ( new int( 1024 ) );

В результате значением pi является адрес созданного объекта, инициализированного числом 1024. С объектом, на который указывает auto_ptr, можно работать обычным

if ( *pi != 1024 )

// ошибка, что-то не так

способом:

else *pi *= 2;

Объект, на который указывает pi, будет автоматически уничтожен по окончании времени жизни pi. Если указатель pi является локальным, то объект, который он адресует, будет уничтожен при выходе из блока, где он определен. Если же pi глобальный, то объект, на который он ссылается, уничтожается при выходе из программы.

Что будет, если мы инициализируем auto_ptr адресом объекта класса, скажем,

auto_ptr< string >

стандартного класса string? Например:

С++ для начинающих

390

pstr_auto( new string( "Brontosaurus" ) );

Предположим, что мы хотим выполнить какую-то операцию со строками. С обычной

string *pstr_type = new string( "Brontosaurus" ); if ( pstr_type->empty() )

строкой мы бы поступили таким образом:

// ошибка, что-то не так

auto_ptr< string > pstr_auto( new

string( "Brontosaurus" ) );

if ( pstr_type->empty() )

 

Акак обратиться к операции empty(), используя объект auto_ptr? Точно так же:

//ошибка, что-то не так

Создатели шаблона класса auto_ptr не в последнюю очередь стремились сохранить привычный синтаксис, употребляемый с обычными указателями, а также обеспечить дополнительные возможности автоматического удаления объекта, на который ссылается auto_ptr. При этом время выполнения не увеличивается. Применение встроенных функций (которые подставляются по месту вызова) позволило сделать использование объекта auto_ptr немногим более дорогим, чем непосредственное употребление указателя.

Что произойдет, если мы проинициализируем pstr_auto2 значением pstr_auto,

// кто несет ответственность за уничтожение строки?

который является объектом auto_ptr, указывающим на строку? auto_ptr< string > pstr_auto2( pstr_auto );

Представим, что мы непосредственно инициализировали один указатель на строку другим:

string *pstr_type2( pstr_type );

Оба указателя теперь содержат адрес одной и той же строки, и мы должны быть внимательными, чтобы не удалить строку дважды.

В противоположность этому шаблон класса auto_ptr поддерживает понятие владения. Когда мы определили pstr_auto, он стал владельцем строки, адресом которой был инициализирован, и принял на себя ответственность за ее уничтожение.

Вопрос в том, кто станет владельцем строки, когда мы инициализируем pstr_auto2 адресом, указывающим на тот же объект, что и pstr_auto? Нежелательно, чтобы оба объекта владели одной и той же строкой: это вернет нас к проблемам повторного удаления, от которых мы стремились уйти с помощью шаблона класса auto_ptr.

Когда один объект auto_ptr инициализируется другим или получает его значение в результате присваивания, одновременно он получает и право владения адресуемым объектом. Объект auto_ptr, стоящий справа от оператора присваивания, передает право

С++ для начинающих

391

владения и ответственность auto_ptr, стоящему слева. В нашем примере ответственность за уничтожение строки несет pstr_auto2, а не pstr_auto. pstr_auto больше не может употребляться для ссылки на эту строку.

auto_ptr< int > p1( new int( 1024 ) );

Аналогично ведет себя и операция присваивания. Пусть у нас есть два объекта auto_ptr: auto_ptr< int > p2( new int( 2048 ) );

Мы можем скопировать один объекта auto_ptr в другой с помощью этой операции:

p1 = p2;

Перед присваиванием объект, на который ссылался p1, удаляется.

После присваивания p1 владеет объектом типа int со значением 2048. p2 больше не может использоваться как ссылка на этот объект.

Третья форма определения объекта auto_ptr создает его, но не инициализирует

// пока не ссылается ни на какой объект

значением указателя на область памяти из хипа. Например: auto_ptr< int > p_auto_int;

Поскольку p_auto_int не инициализирован адресом какого-либо объекта, значение хранящегося внутри него указателя равно 0. Разыменование таких указателей приводит к

// ошибка: разыменование нулевого указателя if ( *p_auto_int != 1024 )

непредсказуемому поведению программы:

*p_auto_int = 1024;

int *pi = 0;

Обычный указатель можно проверить на равенство 0: if ( pi ! = 0 ) ...;

А как проверить, адресует auto_ptr какой-либо объект или нет? Операция get() возвращает внутренний указатель, использующийся в объекте auto_ptr. Значит, мы

// проверяем, указывает ли p_auto_int на объект if ( p_auto_int.get() != 0 &&

*p_auto_int != 1024 )

должны применить следующую проверку:

С++ для начинающих

392

*p_auto_int = 1024;

Если auto_ptr ни на что не указывает, то как заставить его адресовать что-либо? Другими словами, как мы можем присвоить значение внутреннему указателю объекта

else

// хорошо, присвоим ему значение

auto_ptr? Это делается с помощью операции reset(). Например: p_auto_int.reset( new int( 1024 ) );

Объекту auto_ptr нельзя присвоить адрес объекта, созданного с помощью оператора

void example() {

// инициализируется нулем по умолчанию auto_ptr< int > pi;

{

// не поддерживается pi = new int( 5 ) ;

}

new:

}

В этом случае надо использовать функцию reset(), которой можно передать указатель или 0, если мы хотим обнулить объект auto_ptr. Если auto_ptr указывает на объект и является его владельцем, то этот объект уничтожается перед присваиванием нового

auto_ptr< string >

pstr_auto( new string( "Brontosaurus" ) );

// "Brontosaurus" уничтожается перед присваиванием

значения внутреннему указателю auto_ptr. Например: pstr_auto.reset( new string( "Long-neck" ) );

Впоследнем случае лучше, используя операцию assign(), присвоить новое значение

//более эффективный способ присвоить новое значение

//используем операцию assign()

существующей строке, чем уничтожать одну строку и создавать другую: pstr_auto->assign( "Long-neck" );

Одна из трудностей программирования состоит в том, что получить правильный результат не всегда достаточно. Иногда накладываются и временные ограничения. Такая мелочь, как удаление и создание заново строкового объекта, вместо использования

функции assign() при определенных обстоятельствах может вызвать значительное замедление работы. Подобные детали не должны вас беспокоить при проектировании, но при доводке программы на них следует обращать внимание.