Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 65
Текст из файла (страница 65)
Поэтому различные реализации будут реагировать по-разному на некорректпос использование операторов гТе!е1е и с(е(егеП. В простых и неинтересных случаях наподобие предыдущих, компилятор может сам обнаружить проблему, но как правило, неприятности происходят во время выполнения. Спецпшчьный оператор 1(е(е(е(] для массивов не является лап!чески необходимым. Предположим однако, что от реализации управления свободной памятью потребовалн хранения информации для каждо~о объекта о том, является лп он массивом или отдельным ооъектом.
С пользователя было бы снято дополниз ельное бремя, но за счет значительныхх дополнительных затрат времени и памяти в некоторых реализациях С+в . Если массивы в стиле С покажутся вам слишком неудобнымц, воспользуйтесь вместо нцх классом, подобным оес1ог Я 3.7.1, 9 16.3).
Например: ооЫя 'г) иег1ог ТаЫе> и(!О); иеегогкТаЫе>* р = лев вес!от ТаЫе> (!О); // не~и нвоб води,иойгт в Одалении //лредаоитительнее использованы //гкалярньт "де!е1е", а не "Йе!е1е()" де!е1е р, Как правило, лучше избегать подобных сложностей.
Деструктор для каждого созданного элемента массива вызывается прп упнчтожсьши массива. Это происходит неявно для массивов, память под которые не выделялась оператором пепз. Как и в С, в С+в не различаются указатель на отдельный объект и указатель на первый элемент массива Я 5.3). Поэтому программист должен указать, удаляется лп массив илп отдельный объект. Например. оо!еЩ!а!за) 299 10.4. Объекты Использование контейнера (вроде иес1ог) проще, чем запись пар пепэ/еТе!е1е. Более того, пес1огобеспечивает безопасность исключений (приложение Д).
10.4.8. Локальная статическая память Конструктор локального статического объекта (9 7.1.2) вызывается адин раз при первом выполнении инструкции, содержащей определение объекта. Рассмотрим пример: ао! э(,!'(!л1 й ( ета1!с ТаЫе 1(э1; О- эУ (э) ( егаг!с ТаЫе 1Ы2; О. Ы1 эээа!л !) В этом примере конструктор 1Ы вызывается только при первом вызовеу ().
Так как переменная 1Ы объявлена статической, она не уничтожается по возвращению изу" () и не создается повторно при следующем вызове ! (). Ввиду того, что блок, содер:кащий об ьявленпе 1Ы2, не выполняется при вызове 1(0), 1Ы2 не создается до вызова ! (!). И эта переменная не конструируется снова прп втором входе в блок. Деструкторы локальных статических объектов ээызываются в порядке, обратном созданию, при завершении программы (9 9А.1.1). В какой момент точно — не определено. 10.4.9. Нелокальная память Переменная, определенная впе любой функции (то есты лобальная, объявленная в и ространстве имен пли статически в классе, () В.9) инициализируется (конструируется) до вызова эпа!л ().
После выхода из пэа!и () будет вызван деструктор для каждой такой переменной. Динамическая компоновка слегка усложняет зту картину, откладывая инициализацию до того момента, когда код будет скомпонован в исполнимую программу. Конструкторы нелокальных объектов в единице трансляции выполняются в порядке их определений. Рассмотрим пример: с!аее Х( О..
е1аае ТаЫе тет1!э1, ТаЫе 1Ы, Та(э!е Хсэяеаэ1Ы; Глава 10. Классы патезрасе Х ( ТаЫе ЕЫ2; Порялок создания следующии: ЕЫ, затем Хстет(Ы и затем Хс(Ы2. Обратите внимание, что объявление (в отличие от определения), наподобие объявления шетЕЫ в Х, не влияет на порялок создания.
Деструкторы вызываются в порядке, обратном копгтрукторам: Хс(б!2, Ххтесп(Ы, ЕЫ. Не дается никаких гарантий по поводу порядка конструирования нелокальных объектов из различных единиц компиляции. Например: ///Ие Е сп ТаЫе ЕЫ1, с1авзХ1сЬ гпп( ХИЬ тп((; -ХЕЕЬ тИ(), // подеотонливает ХЕЕЬ к использованию // производит очитпку после использования ХЬЬ с(азз ХИЬ ( ЫапсХИЬ Епсгк; 0- !' К сожалению, не гарантируется, что такой объект будет проинипиализирован до его первого использования и уничтожен после последнего использования в программе, состоящей из раздельно компилируемых единиц. Конкретная реализация Сч-+ может предоставить такую гарантию, но большинство из ннх этого не делает. Программист может обеспечить правильную инициализацию, придерживаясь обычной стратегии для локальных статических объектов: использовать индикатор первого вызова.
Например: с1аев ХИЬ ( зсапс Ьоо1 ЕпЕЕЕаЕЕяесЕ; в!ассе иоЫ ганса((ве (( ( /* и ни уиилизси(ия */ ЕпгдаЕгвед = Егие; ) риЬИа // конструктор отсутствует иоиЩ( //~ясе2.с ТаЫе 1612; Что будет создано раньше — ЕЫ1 илп ЕЫ2 — зависит от реализации. Не гарантируется даже, что в одной и той же реализации гюрядок будет постоянным. Динамическая компоновка или даже небольшое изменение в процессе компиляции может изменить последовательность создания.
Аналогично, порядок уничтожения также зависит от реализации. Иногда при создании библиотеки необходимо илн просто удобно изобрести тип с конструктором и деструктором, единственной целью которого является инициализация и последующая очистка. Такой тип будет использован только один раз: для выделения памяти под статический объект, так чтобы при этом вызвались конструктор и деструктор.
Например: 10.4. Объекты 301 (!' (!и!г!а!!ее!! ==Ха(яе) ть пайве (); // Если во многих функциях требуется проверка индикатора первого вызова, этот метод может стать довольно утомительным, но тем не менее он работает. Эта техника использует тот факт, что статически размещаемые объекты без конструкторов инициализируютсяя нулем. Действительно сложным является случай, когда первая операция критична по времени, и поэтому дополнительные затраты на проверку и возможную инициализацию могут оказаться серьезной проблемой. В подобных ситуациях потребуются дополнительные трюки Я 21.5.2).
Альтернативным подходом в случае простых объектов является представление их в виде функций (2 9.4.1): (пя оЬ!()(я!апс/и!я=О, гегигпх;) //инициилизиция при первом вызове Индикаторы первого вызова не решают всех возможных проблем. Например, можно создать объекты, которые ссылаются друг на друга во время конструирования. Таких случаев лучше избегать, но если такие объекты необходимы, их нужно конструировать аккуратно и поэтапно. Отметим также, что не существует простой аналогичной конструкции индикатора последнего вызова (см. 2 9.4.1.1 и 9 21.5.2). 10.4.10. Временные объекты Временные объекты чаще всего возникают при вычислении арифметических выражений.
Например, в некоторый момент вычисления х*уз-ж надо где-то сохранить промежуточный результат х*у. За исключением случаев, когда скорость выполнения крайне важна Я 11.6), программист редко обращает внимание на временные объекты. Но иногда приходится зто делать Я 11.6, 9 22.4.7). Если временный объект не связан со ссылкой или не используется для инициализации именованного объекта, он уничтожается по достижении конца полного выражения, в котором был создан. Ивяное выралсеиие — это выражение, которое не является частью другого выражения.
Стандартный класс я!г(поимеет функцию- !лен с я/г (), которая возвращает указатель на массив символов в стиле С с завершающим нулем (9 3,5.1, 9 20А.1). Кроме того, определен оператор ж, означа!оший конкатенацию строк. Эти операпии являются очень важными прп работе со строками. Однако при совместном их использовании, они могут привести к странным проблемам. Например: ооИ1(ягг/пдй я!, я!Нпуя я2, я!г!пуо яз) сопя! сдаг* ся = (ятея2).с я!г (); сои! «ся; (!' (я!г!еп (ся= (я2+яз) с яа ())<!! Я', ся(0)=='а ) ( ,// использоваиие ся ) ) 302 Глава 10.
Классы Возможно, вашей первой реакцией будет «просто не пиши так, да и все», н я соглашусь. Однако такой код встречается, поэтому стоит знать, как он интерпретируется. Создается временный объект класса и1ппд для хранения и1+и2. Затем из объекта извлекается указатель на С-строку. Затем — в конце выражения — временный объект уничтожается.
Где была размещена возвращаемая функцией с и(г (( С-строка? Вероятно, в части временного объекта, хранившего и!»и2, но эта память не обязана существовать после уничтожения временной переменной. Следовательно, си указывает на освобожденную память. Возможно, сои1«си и сработает так, как ц ожидалось, но это будет просто удачей. Компилятор может обнаружить и предупредить о больпшнствс подобных проблем. Пример с у иашорукипей гораздо тоньше. Условная инструкция отработает так, как п ожидалось, потому что полным выражением, в котором создается временная переменная для хранения и2»иВ, являетгя само условие.
Однако эта временная переменная будет уничтожена до выполнения следующей (контролируемой) инструкции, поэтому пе гарантируется правильность последующего использования си. Обратите внимание, что в этом случае, как и во мнопщ подобных, проблема с временными переменными возникла пз-за низкоуровнего непользования типов данных высокого уровня. Более «чистый» стиль программирования приводит пе только к более понятным фрагмснтам кода, но также позволяет полностью избежать проблем с временными переменными.