С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 64
Текст из файла (страница 64)
Динамически размещаемые объектыВремя жизни глобальных и локальных объектов четко определено. Программистнеспособен хоть как-то изменить его. Однако иногда необходимо иметь объекты,временем жизни которых можно управлять. Выделение памяти под них и ееосвобождение зависят от действий выполняющейся программы. Например, можноотвести память под текст сообщения об ошибке только в том случае, если ошибкадействительно имела место. Если программа выдает несколько таких сообщений, размервыделяемой строки будет разным в зависимости от длины текста, т.е.
подчиняется типуошибки, произошедшей во время исполнения программы.Третий вид объектов позволяет программисту полностью управлять выделением иосвобождением памяти. Такие объекты называют динамически размещаемыми или, длякраткости, просто динамическими. Динамический объект “живет” в пуле свободнойпамяти, называемой хипом. Программист создает его с помощью оператора new, ауничтожает с помощью оператора delete. Динамически размещаться может какединичный объект, так и массив объектов. Размер массива, размещаемого в хипе,разрешается задавать во время выполнения.С++ для начинающихВ этом разделе, посвященном динамическим объектам, мы рассмотрим три формыоператора new: для размещения единичного объекта, для размещения массива и третьюформу, называемую оператором размещения new (placement new expression).
Когда хиписчерпан, этот оператор возбуждает исключение. (Разговор об исключениях будетпродолжен в главе 11. В главе 15 мы расскажем об операторах new и deleteприменительно к классам.)8.4.1. Динамическое создание и уничтожение единичныхобъектовОператор new состоит их ключевого слова new, за которым следует спецификатор типа.Этот спецификатор может относиться к встроенным типам или к типам классов.Например:new int;размещает в хипе один объект типа int. Аналогично в результате выполненияинструкцииnew iStack;там появится один объект класса iStack.Сам по себе оператор new не слишком полезен. Как можно реально воспользоватьсясозданным объектом? Одним из аспектов работы с памятью из хипа является то, чторазмещаемые в ней объекты не имеют имени.
Оператор new возвращает не сам объект, ауказатель на него. Все манипуляции с этим объектом производятся косвенно черезуказатели:int *pi = new int;Здесь оператор new создает один объект типа int, на который ссылается указатель pi.Выделение памяти из хипа во время выполнения программы называется динамическимвыделением. Мы говорим, что память, адресуемая указателем pi, выделена динамически.Второй аспект, относящийся к использованию хипа, состоит в том, что эта память неинициализируется. Она содержит “мусор”, оставшийся после предыдущей работы.Проверка условия:if ( *pi == 0 )вероятно, даст false, поскольку объект, на который указывает pi, содержит случайнуюпоследовательность битов.
Следовательно, объекты, создаваемые с помощью оператораnew, рекомендуется инициализировать. Программист может инициализировать объекттипа int из предыдущего примера следующим образом:int *pi = new int( 0 );Константа в скобках задает начальное значение для создаваемого объекта; теперь piссылается на объект типа int, имеющий значение 0. Выражение в скобках называетсяинициализатором. Это может быть любое выражение (не обязательно константа),возвращающее значение, приводимое к типу int.386С++ для начинающих387Оператор new выполняет следующую последовательность действий: выделяет из хипапамять для объекта, затем инициализирует его значением, стоящим в скобках.
Длявыделения памяти вызывается библиотечная функция new(). Предыдущий операторint ival = 0;// создаем объект типа int и инициализируем его 0приблизительно эквивалентен следующей последовательности инструкций:int *pi = &ival; // указатель ссылается на этот объектне считая, конечно, того, что объект, адресуемый pi, создается библиотечной функциейnew() и размещается в хипе. АналогичноiStack *ps = new iStack( 512 );создает объект типа iStack на 512 элементов.
В случае объекта класса значение илизначения в скобках передаются соответствующему конструктору, который вызывается вслучае успешного выделения памяти. (Динамическое создание объектов классов болееподробно рассматривается в разделе 15.8. Оставшаяся часть данного раздела посвященасозданию объектов встроенных типов.)Описанные операторы new могут вызывать одну проблему: хип, к сожалению, являетсяконечным ресурсом, и в некоторой точке выполнения программы мы можем исчерпатьего. Если функция new() не может выделить затребованного количества памяти, онавозбуждает исключение bad_alloc. (Обработка исключений рассматривается в главе 11.)Время жизни объекта, на который указывает pi, заканчивается при освобождениипамяти, где этот объект размещен. Это происходит, когда pi передается операторуdelete. Например,delete pi;освобождает память, на которую ссылается pi, завершая время жизни объекта типа int.Программист управляет окончанием жизни объекта, используя оператор delete внужном месте программы.
Этот оператор вызывает библиотечную функцию delete(),которая возвращает выделенную память в хип. Поскольку хип конечен, очень важновозвращать ее своевременно.Глядя на предыдущий пример, вы можете спросить: а что случится, если значение pi по// необходимо ли это?if ( pi != 0 )какой-либо причине было нулевым? Не следует ли переписать этот код таким образом:delete pi;Нет. Язык С++ гарантирует, что оператор delete не будет вызывать функцию delete()в случае нулевого операнда. Следовательно, проверка на 0 необязательна. (Если вы явнодобавите такую проверку, в большинстве реализаций она фактически будет выполненадважды.)Важно понимать разницу между временем жизни указателя pi и объекта, который онадресует. Сам объект pi является глобальным и объявлен в глобальной областиС++ для начинающихвидимости.
Следовательно, память под него выделяется до выполнения программы исохраняется за ним до ее завершения. Совсем не так определяется время жизниадресуемого указателем pi объекта, который создается с помощью оператора new вовремя выполнения. Область памяти, на которую указывает pi, выделена динамически,следовательно, pi является указателем на динамически размещенный объект типа int.Когда в программе встретится оператор delete, эта память будет освобождена. Однакопамять, отведенная самому указателю pi, не освобождается, а ее содержимое неизменяется. После выполнения delete объект pi становится висячим указателем, то естьссылается на область памяти, не принадлежащую программе. Такой указатель служитисточником трудно обнаруживаемых ошибок, поэтому сразу после уничтожения объектаему полезно присвоить 0, обозначив таким образом, что указатель больше ни на что нессылается.Оператор delete может использоваться только по отношению к указателю, которыйсодержит адрес области памяти, выделенной в результате выполнения оператора new.Попытка применить delete к указателю, не ссылающемуся на такую память, приведет кнепредсказуемому поведению программы.
Однако, как было сказано выше, этот операторможно применять к нулевому указателю.void f() {int i;string str = "dwarves";int *pi = &i;short *ps = 0;double *pd = new doub1e(33);deletedeletedeletedeletestr; // плохо: str не является динамическим объектомpi; // плохо: pi ссылается на локальный объектps; // безопасноpd; // безопасноНиже приведены примеры опасных и безопасных операторов delete:}Вот три основные ошибки, связанные с динамическим выделением памяти:•не освободить выделенную память. В таком случае память не возвращается вхип.
Эта ошибка получила название утечки памяти;•дважды применить оператор delete к одной и той же области памяти. Такоебывает, когда два указателя получают адрес одного и того же динамическиразмещенного объекта. В результате подобной ошибки мы вполне можем удалитьнужный объект. Действительно, память, освобожденная с помощью одного изадресующих ее указателей, возвращается в хип и затем выделяется под другойобъект. Затем оператор delete применяется ко второму указателю, адресовавшемустарый объект, а удаляется при этом новый;•изменять объект после его удаления. Такое часто случается, посколькууказатель, к которому применяется оператор delete, не обнуляется.Эти ошибки при работе с динамически выделяемой памятью гораздо легче допустить,нежели обнаружить и исправить.
Для того чтобы помочь программисту, стандартнаябиблиотека С++ представляет класс auto_ptr. Мы рассмотрим его в следующемподразделе. После этого мы покажем, как динамически размещать и уничтожатьмассивы, используя вторую форму операторов new и delete.388С++ для начинающих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? Например:389С++ для начинающих390pstr_auto( new string( "Brontosaurus" ) );Предположим, что мы хотим выполнить какую-то операцию со строками. С обычнойstring *pstr_type = new string( "Brontosaurus" );if ( pstr_type->empty() )строкой мы бы поступили таким образом:// ошибка, что-то не такauto_ptr< string > pstr_auto( newif ( pstr_type->empty() )string( "Brontosaurus" ) );А как обратиться к операции empty(), используя объект auto_ptr? Точно так же:// ошибка, что-то не такСоздатели шаблона класса auto_ptr не в последнюю очередь стремились сохранитьпривычный синтаксис, употребляемый с обычными указателями, а также обеспечитьдополнительные возможности автоматического удаления объекта, на который ссылаетсяauto_ptr.