С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 65
Текст из файла (страница 65)
При этом время выполнения не увеличивается. Применение встроенныхфункций (которые подставляются по месту вызова) позволило сделать использованиеобъекта 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, стоящий справа от оператора присваивания, передает правоС++ для начинающихвладения и ответственность 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 )должны применить следующую проверку:391С++ для начинающих*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() при определенных обстоятельствах может вызвать значительноезамедление работы. Подобные детали не должны вас беспокоить при проектировании, нопри доводке программы на них следует обращать внимание.392С++ для начинающихШаблон класса auto_ptr обеспечивает значительные удобства и безопасностьиспользования динамически выделяемой памяти. Однако все равно надо не терятьбдительности, чтобы не навлечь на себя неприятности:•нельзя инициализировать объект auto_ptr указателем, полученным не спомощью оператора new, или присвоить ему такое значение. В противном случаепосле применения к этому объекту оператора delete поведение программынепредсказуемо;•два объекта auto_ptr не должны получать во владение один и тот же объект.Очевидный способ допустить такую ошибку – присвоить одно значение двумauto_ptr< string >pstr_auto( new string( "Brontosaurus" ) );// ошибка: теперь оба указывают на один объект// и оба являются его владельцамиобъектам.
Менее очевидный – с помощью операции get(). Вот пример:auto_ptr< string > pstr_auto2( pstr_auto.get() );Операция release() гарантирует, что несколько указателей не являютсявладельцами одного и того же объекта. release() не только возвращает адресобъекта, на который ссылается auto_ptr, но и передает владение им.// правильно: оба указывают на один объект,// но pstr_auto больше не является его владельцемauto_ptr< string >Предыдущий фрагмент кода нужно переписать так:pstr_auto2( pstr_auto.release() );8.4.3. Динамическое создание и уничтожение массивовОператор new может выделить из хипа память для размещения массива. В этом случаепосле спецификатора типа в квадратных скобках указывается размер массива. Он можетбыть задан сколь угодно сложным выражением.
new возвращает указатель на первый// создание единственного объекта типа int// с начальным значением 1024int *pi = new int( 1024 );// создание массива из 1024 элементов// элементы не инициализируютсяint *pia = new int[ 1024 ];// создание двумерного массива из 4x1024 элементовэлемент массива. Например:int (*pia2)[ 1024 ] = new int[ 4 ][ 1024 ];pi содержит адрес единственного элемента типа int, инициализированного значением1024; pia – адрес первого элемента массива из 1024 элементов; pia2 – адрес начала393С++ для начинающих394массива, содержащего четыре массива по 1024 элемента, т.е.
pia2 адресует 4096элементов.В общем случае массив, размещаемый в хипе, не может быть инициализирован. (Вразделе 15.8 мы покажем, как с помощью конструктора по умолчанию присвоитьначальное значение динамическому массиву объектов типа класса.) Задаватьинициализатор при выделении оператором new памяти под массив не разрешается.Массиву элементов встроенного типа, размещенному в хипе, начальные значенияfor (int index = 0; index < 1024; ++index )присваиваются с помощью цикла for:pia[ index ] = 0;Основное преимущество динамического массива состоит в том, что количество элементовв его первом измерении не обязано быть константой, т.е. может не быть известным вовремя компиляции. Для массивов, определяемых в локальной или глобальной областивидимости, это не так: здесь размер задавать необходимо.Например, если указатель в ходе выполнения программы ссылается на разные C-строки,то область памяти под текущую строку обычно выделяется динамически и ее размеропределяется в зависимости от длины строки.
Как правило, это более эффективно, чемсоздавать массив фиксированного размера, способный вместить самую длинную строку:ведь все остальные строки могут быть значительно короче. Более того, программа можетаварийно завершиться, если длина хотя бы одной из строк превысит отведенный лимит.Оператор new допустимо использовать для задания первого измерения массива спомощью значения, вычисляемого во время выполнения. Предположим, у нас естьconst char *noerr = "success";// ...const char *err189 = "Error: a function declaration must "следующие C-строки:"specify a function return type!";Размер создаваемого с помощью оператора new массива может быть задан значением,#include <cstring>const char *errorTxt;if (errorFound)errorTxt = errl89;elseerrorTxt = noerr;int dimension = strlen( errorTxt ) + 1;char *strl = new char[ dimension ];// копируем текст ошибки в strlвычисляемым во время выполнения:strcpy( strl, errorTxt );С++ для начинающих// обычная для С++ идиома,// иногда удивляющая начинающих программистовdimension разрешается заменить выражением:char *strl = new char[ str1en( errorTxt ) + 1 ];Единица, прибавляемая к значению, которое возвращает strlen(), необходима для учетазавершающего нулевого символа в C-строке.
Отсутствие этой единицы – весьмараспространенная ошибка, которую достаточно трудно обнаружить, поскольку онапроявляет себя косвенно: происходит затирание какой-либо другой области программы.Почему? Большинство функций, которые обрабатывают массивы, представляющие собойС-строки символов, пробегают по элементам, пока не встретят завершающий нуль.Если в конце строки нуля нет, то возможно чтение или запись в случайную областьпамяти. Избежать подобных проблем позволяет класс string из стандартной библиотекиС++.Отметим, что только первое измерение массива, создаваемого с помощью оператора new,может быть задано значением, вычисляемым во время выполнения. Остальные измеренияint getDim();// создание двумерного массиваint (*pia3)[ 1024 ] = new int[ getDim() ][ 1024 ]; // правильно// ошибка: второе измерение задано не константойдолжны задаваться константами, известными во время компиляции. Например:int **pia4 = new int[ 4 ][ getDim() ];Оператор delete для уничтожения массива имеет следующую форму:delete[] str1;Пустые квадратные скобки необходимы.