Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 57
Текст из файла (страница 57)
Однако я настаивал на том, что массив объектов Х вЂ” не то же самое, что Х, а стало быль,использовать распределитель для класса Х нельзя. Вели бы его можно было применять к массивам, то автору х:: орегасох пе»/( ) пришлось бы специально рассматривать выделение памяти для массива «па всякий случай» и тем самым усложнять алгоритм для стандартной ситуации. А если алгоритм не является настолько нужным, то зачем вообще писать специализированный распределитель? Кроме того, я подчеркнул, что управления выделением памяти для одномерных массивов типа Х [д] вес равно недостаточно: как иногда быть с многомерными массивами Х [с)1] [с)2]? Однако отсутствие механизма управления выделением памяти для массивов создавало неудобства в реальных ситуациях, так что комитет по стандартизации, в конце концов, предложил решение проблемы.
Самым неприятным было то, что, с одной стороны, пользователю нельзя было запретить распределять массив в свободной памяти, с другой стороны, не было способа контролировать такое распределение. В системах, опирающихся на логически различные схемы управления '16>ИИИИИ) Размещение объекта в памяти памятью, это могло стать причиной серьезных трудностей, поскольку пользователь по наивности размешал большие динамические массивы в области, предназначенной для размещения объектов.
Решение заключалось в том, чтобы просто опрелелить пару функций спепиально для выделения и освобождения памяти под массивы: с1авв Х ( // чоЫ* оретатот пеы(вьге т вг); чо1б орегатог бе1ете(чоЫ* р); выделение памяти дпя объектов чоЫ* орегагог пеи(!(вгге т вг); // выделение памяти дпя массивов чоЫ оретагот ((е1ете[](чотб' р); ); Распределитель памяти для массивов применим к массивам любой размерности. Как и любой другой распрелелитель, орегасог печ/ [] должен предоставить запрошенное число байт; и не имеет значения, как будет использоваться зта память.
В частности, распределителю не нужно задавать ни размерность массива, ни число злементов в нем. На введении операторов для выделения и освобождения памяти под массивы больше других настаивала Лаура Йекер [].ауга Уакег) из компании Меп(ог Стар]((св. 10.4. Размещение объекта в памяти С помощью единого механизма были решены две смежные проблемы: л нужен механизм размещения объекта по фиксированному адресу. Например, объект, описывающий процесс, следует разместить по адресу, который диктует специализированная аппаратура; ы требуется механизм распределения памяти для объекта из конкретной арены. Например, нужно разместить объект в разделяемой памяти многопроцессорной системы или в области, управляемой диспетчером устойчивых объектов. чоЫ* орегатот пеи(в1ге т, чоЫ* р) гетцгп р; // разместить объект по адресу 'р' ) а вызывать следующим образом: чоЫ' Ьоб = (чоЫ*)ОкГООГ; // специальный адрес Х* р2 = пеи(Ьцб)Х/ // конструируем Х в области 'Ьцг' вызывается орегатог пеы(в1вео'(х),Ьцг) Решение — разрешить перегрузку орегагог печ/(] и предоставить синтаксис для перелачи оператору пем дополнительных аргументов.
Например, орегагог пеы () для размещения объекта по фиксированному адресу можно было определить так: ЕИИИИИ6~ Управление памятью чсьб* срс ассг лен(аъге Г з, галс агелаа а) ( гегагл а.а11сс(з); ) а использовать слелуюшим образом: чо1«( б(Гавг агелаа агапа) х* р = лен(агапа)х; // распределить х на арене ) Злесь прелполагается, что йазс агепа — это класс, который имеетфупкциючлен а11ос (), применяюшуюся лля вылеления памяти. Например: с1ааа Ганс агапа ( // слаг* щахр; сваг* плеер; спаг* ехралб(ваге Г в); // получить дополнительную память от // распределителя общего назначения раю11с: чоЫ* а11ос(атге г а) ( с)1аг* р — бгеер.
геьагл ((ггеер+=а)<юахр) з р : ехралб(а) ) чо1б Егее(чо1с)') () // ничего не делает с1еаг(); // освободить всю выделенную память Такая арена спец изльно прелназначена лля быстрого вылеления памяти и почти мгновенного ее освобождения.
Важной областью применения арен является прелоставление специальной семантики управления памятью. 10.5. Проблемы освобождения памяти Межлуоператорами орегасог пемп и орегасог ((е1есе() имеетсяочевидная и намеренная асимметрия: первый можно перегружать, второй — нет. Это Из-за такого использования синтаксис пем (Ьий ) х лля перелачи дополнительных аргументов оператору орегаеот пем ( ) получил название «синтаксис размещениям Отметим, что первым аргументом любого орегасог пем ( ) является объем вылеляемой памяти, и в ланном случае размер объекта подставляется системой неявно.
В то время я, мягко говоря, нелооценил важность оператора размещения. При наличии размешения оператор пем перестает быть просто механизмом распределения памяти. Поскольку с фиксированным адресом можно ассоциировать произвольные логические свойства, то пем приобретает черты универсального менеджера ресурсов. Для конкретной арены орегасог пем ( ) можно определить так: Проблемы освобождения памяти сравнимо с аналогичной асимметрией между конструкторами и деструкторами.
Таким образом, при создании обьекта вы можете выбрать, скажем, олин из четырех распрелелителей памяти и один из пяти конструкторов, но во время уничтожения объекта в вашем распоряжении будет лишь олин способ: бе1еге р; Причина в том, что при созлании объекта вы знаете о нем все, а при уничтожении имеется лишь указатель, который может принадлежать или не принадлежать точному типу объекта. В случаях, когда пользователь уничтожает объект производного класса, пользуясь указателем на объект базового класса, не обойтись без виртуальных деструкторов: с1авв Х чтгпиа1 -Х(); с1авв у : рпытс Х ( // -т (); чоЫ Г(х* р1) ( Х* р2 = пеы у; г)е1есе р2; // вызывается у::-у с)е1есе р1; // вызывается правильный деструктор, // каким бы он ни оказался ) Гарантируется также, что если в иерархии классов определены локальные операторы орепасог с)е1есе (), то булет вызван нужный.
Если бы виртуальный леструктор не использовался, то лействия по очистке, заданные в леструкторе класса у, никогла не выполнялись бы. Однако для выбора функции освобожления памяти в языке нет средств, парных механизму выбора функции распределения: с1авв х ( // чоЫ" орегагог пеи(втзе с); // обычное выделение памяти чоЫ' орегапог пеи(ьтзе и, Агепаа); // из арены дгепа чоЫ орегапох де!епе(чоЫ*); // нельзя определить чоЫ орегаеог бе1есе(чоЫ*, )(гепаа); ); Вель нельзя прелполагатгь что в точке уничтожения объекта известно, как он был создан. Идеальный вариант — тот, при котором пользователю вообше не нужно освобождать память, занятую объектом. Для этого, в частности, применяются ЕИИИИИИЕ Управление памятью специальные арены.
Арену можно определить так, что в некоторой известной точке программы будет освобохсдаться вся память, занятая этой программой. Можно также написать лля нее сборщик мусора. Первый подход очень распространен, второй — нет. Специальный сборшик мусора должен быть написан удачно, иначе проша встроить в программу стандартный [Вое)1т, 1993]. Чаще прп программировании функций оретатот пеы ( ) оставляют некоторый признак, который доступен при вызове оретасот де1есе () и показывает, как следует освобождать память. Заметим, что все это относится к управлению памятью, следовательно, находится на более низком уровне, чем создание объектов с помошью конструкторов и их уничтожение деструкторами. Поэтому область памяти, гле хранится такой признак, не является частью объекта, но как-то связана с нпм.
Например, оретасот пеы() мог бы поместить информацию, относя1пуюся к управлению памятью, в слово, предшествуюп(ее тому, на которое указывает возвращенное им значение. Также оретасот пеы() мог бы оставить эту информацию там, где конструкторы или другие функции имеют возможность найти ее и выяснить, распределена лн память для объекта из кучи. Был /ш запрет на перегрузку с(е1ете ( ) ошибкой? Не уверен, но думаю, что этот случай из разряда тех, гле любое решение порожлает проблемы.
Возможность вызывать деструктор явно была введена в версии 2.0 для тех моментов, когда выделение и освобождение памяти абсолютно разъединены. Примером л(ожет служить контейнер, который предназначен для полного управления памятью храпяшихся в нем объектов. чоъо 1(Х* р1) // р1 может указывать на единичный // объект или на массив ( Х* р2 = пем Х(10); ) // р2 указывает на массив Как люжно гарантировать правильное удаление массива? В частности, как гарантировать, что для каждого элемента массива вызван деструктор? В версии 1.0 не было удовлетворительного ответа на этп вопросы. В версии 2.0 появился оператор удаления массива с[е1е се [1: чола ((Х* р1)// р1 может указывать на единичный // объект или на массив ( Х* р2 = пеы Х[10]; // де1есе р2; // р2 указывает на массив // ошибка: р2 указывает на массив 10.5Л. Освобождение памяти для массивов В С++ единичньш объект, обозначенный указателем, вполне может оказаться первым элементом массива.
Так же было и в С. Если указатель указывает на первый элемент массива, то обычно говорят, что он указывает на массив. Обычно компилятор не может отличить указатели на единичный объект н на массив. Выделенно и освобождение памяти лля массивов производится именно через такие указатели. Например: 'ФИИИИИИИ Нехватка памяти с1е1есе() р?; с1е1есе р1; беге се [) р1; // правильно // может, и правильно // может, и правильно С помошькг йе1есе не всегда возможно освобождать память п для едшшчных обьектов, и для массивов. Это помогает избежать лишних сложностей при обработке наиболее частого случая выделения и освобождения памяти для одного оръекта, а также позволяет не загромождать объекты информацией, необходимой только для правильного удаления массивов. В промежуточном варианте с)е1есе [] программист должен был указывать число элементов в массиве, например: де1еге[10) р2г Это слишком часто приводило к ошибкам, поэтому за хранение информации о числе элементов в массиве отвечал компилятор.
10.6. Нехватка памяти Невозможность получить запрошенный ресурс — распространеггная пробяема. Еггге до появления версии 2.0 стало понятно, что ее решение следует искать в области обработки исключений (см, раздел 3.15). Олнако до обработки исключений в общем виде (см. главу 16) было еше далеко, а конкретную проблему нехватки намял следовало решать немедленно. Для переходного периода, растянувшегося на несколько лет, нужно было предложить хоть какое-то решение. Самыми животрепещущими являлись две проблемы: сг пользователю следует передать управление во всех случаях, когда вызов библиотечной функции завершается неудачей из-за нехватки памяти (или по любой другой причине).
Это непререкаемое требование исходило от пользователей, работающих в Атб Т; гд нельзя требовать, чтобы средний пользователь проверял ошибку после выполнения каждой операции выделения памяти. Практика работы с С показывает, что пользователи не проводят такую проверку систематически, даже если она необходима. чоган Г() ( Х* р = лен Х; 1( (р==е) ( // обработать ожибку выделения памяти // конструктор не вызывается ) // использовать р ) Первое требование было удовлетворено тем, что при нехватке памяти конструктор не вызывался, а орекасол печг () возвращал О.