Породите и обработайте ошибки динамического выделения памяти переполнения очереди
Динамическое и статическое выделение памяти. Преимущества и недостатки. Выделение памяти для одиночных переменных операторами new и delete . Возможные критические ситуации при выделении памяти. Инициализация при выделении памяти
Содержание
- 1. Динамическое и статическое (фиксированное) выделение памяти. Главные различия
- 2. Преимущества и недостатки использования динамического и статического способов выделения памяти
- 3. Как выделить память оператором new для одиночной переменной? Общая форма.
- 4. Как освободить память, выделенную под одиночную переменную оператором delete ? Общая форма
- 5. Примеры выделения ( new ) и освобождения ( delete ) памяти для указателей базовых типов
- 6. Что такое «утечка памяти» (memory leak)?
- 7. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуацияbad_alloc . Пример
- 8. Выделение памяти для переменной с одновременной инициализацией. Общая форма. Пример
Поиск на других ресурсах:
1. Динамическое и статическое (фиксированное) выделение памяти. Главные различия
Для работы с массивами информации, программы должны выделять память для этих массивов. Для выделения памяти под массивы переменных используются соответствующие операторы, функции и т.п.. В языке программирования C++ выделяют следующие способы выделения памяти:
1. Статическое (фиксированное) выделение памяти. В этом случае память выделяется только один раз во время компиляции. Размер выделенной памяти есть фиксированным и неизменным до конца выполнения программы. Примером такого выделения может служить объявление массива из 10 целых чисел:
2. Преимущества и недостатки использования динамического и статического способов выделения памяти
Динамическое выделение памяти по сравнению со статическим выделением памяти дает следующие преимущества:
- память выделяется по мере необходимости программным путем;
- нет лишних затрат неиспользованной памяти. Выделяется столько памяти сколько нужно и если нужно;
- можно выделять память для массивов информации, размер которых заведомо неизвестен. Определение размера массива формируется в процессе выполнения программы;
- удобно осуществлять перераспределение памяти. Или другими словами, удобно выделять новый фрагмент для одного и того же массива, если нужно выделить дополнительную память или освободить ненужную;
- при статическом способе выделения памяти трудно перераспределять память для переменной-массива, поскольку она уже выделена фиксировано. В случае динамического способа выделения, это делается просто и удобно.
Преимущества статического способа выделения памяти:
- статическое (фиксированное) выделение памяти лучше использовать, когда размер массива информации заведомо известен и есть неизменным на протяжении выполнения всей программы;
- статическое выделение памяти не требует дополнительных операций освобождения с помощью оператора delete . Отсюда вытекает уменьшение ошибок программирования. Каждому оператору new должен соответствовать свой оператор delete ;
- естественность (натуральность) представления программного кода, который оперирует статическими массивами.
В зависимости от поставленной задачи, программист должен уметь правильно определить, какой способ выделения памяти подходит для той или другой переменной (массива).
3. Как выделить память оператором new для одиночной переменной? Общая форма.
Общая форма выделения памяти для одиночной переменной оператором new имеет следующий вид:
- ptrName – имя переменной (указателя), которая будет указывать на выделенную память;
- type – тип переменной. Размер памяти выделяется достаточный для помещения в нее значения переменной данного типа type .
4. Как освободить память, выделенную под одиночную переменную оператором delete ? Общая форма
Общая форма оператора delete для одиночной переменной:
где ptrName – имя указателя, для которого была раньше выделена память оператором new . После выполнения оператора delete указатель ptrName указывает на произвольный участок памяти, который не является зарезервированным (выделенным).
5. Примеры выделения ( new ) и освобождения ( delete ) памяти для указателей базовых типов
В примерах демонстрируется использование операторов new и delete . Примеры имеют упрощенный вид.
Пример 1. Указатель на тип int . Простейший пример
Пример 2. Указатель на тип double
⇑
7. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуация bad_alloc . Пример
При использовании оператора new возможна ситуация, когда память не выделится. Память может не выделиться в следующих ситуациях:
- если отсутствует свободная память;
- размер свободной памяти меньше чем тот, который был задан в операторе new .
В этом случае генерируется исключительная ситуация bad_alloc . Программа может перехватить эту ситуацию и соответствующим образом обработать ее.
Пример. В примере учитывается ситуация, когда память может не выделиться оператором new . В таком случае осуществляется попытка выделить память. Если попытка удачная, то работа программы продолжается. Если попытка завершилась неудачей, то происходит выход из функции с кодом -1.
8. Выделение памяти для переменной с одновременной инициализацией. Общая форма. Пример
Оператор выделения памяти new для одиночной переменной допускает одновременную инициализацию значением этой переменной.
В общем, выделение памяти для переменной с одновременной инициализацией имеет вид
- ptrName – имя переменной-указателя, для которой выделяется память;
- type – тип на который указывает указатель ptrName ;
- value – значение, которое устанавливается для выделенного участка памяти (значение по указателю).
Пример. Выделение памяти для переменных с одновременной инициализацией. Ниже приводится функция main() для консольного приложения. Продемонстрировано выделение памяти с одновременной инициализацией. Также учитывается ситуация, когда попытка выделить память завершается неудачей (критическая ситуация bad_alloc ).
Статическое выделение памяти выполняется для статических и глобальных переменных. Память выделяется один раз (при запуске программы) и сохраняется на протяжении работы всей программы.
Автоматическое выделение памяти выполняется для параметров функции и локальных переменных. Память выделяется при входе в блок, в котором находятся эти переменные, и удаляется при выходе из него.
Динамическое выделение памяти является темой этого урока.
Динамическое выделение переменных
Как статическое, так и автоматическое распределение памяти имеют два общих свойства:
Размер переменной/массива должен быть известен во время компиляции.
Выделение и освобождение памяти происходит автоматически (когда переменная создается/уничтожается).
В большинстве случаев с этим всё ОК. Однако, когда дело доходит до работы с пользовательским вводом, то эти ограничения могут привести к проблемам.
Например, при использовании строки для хранения имени пользователя, мы не знаем наперед насколько длинным оно будет, пока пользователь его не введет. Или нам нужно создать игру с непостоянным количеством монстров (во время игры одни монстры умирают, другие появляются, пытаясь, таким образом, убить игрока).
Если нам нужно объявить размер всех переменных во время компиляции, то самое лучшее, что мы можем сделать — это попытаться угадать их максимальный размер, надеясь, что этого будет достаточно:
char name [ 30 ] ; // будем надеяться, что пользователь введет имя длиной менее 30 символов! Polygon rendering [ 40000 ] ; // этому 3D-рендерингу лучше состоять из менее чем 40000 полигонов!Это плохое решение, по крайней мере, по трем причинам:
Во-первых, теряется память, если переменные фактически не используются или используются, но не все. Например, если мы выделим 30 символов для каждого имени, но имена в среднем будут занимать по 15 символов, то потребление памяти получится в два раза больше, чем нам нужно на самом деле. Или рассмотрим массив rendering : если он использует только 20 000 полигонов, то память для других 20 000 полигонов фактически тратится впустую (т.е. не используется)!
В Visual Studio это можно проверить, запустив следующий фрагмент кода:
int array [ 1000000000 ] ; // выделяем 1 миллиард целочисленных значенийЛимит в 1МБ памяти может быть проблематичным для многих программ, особенно где используется графика.
Для динамического выделения памяти одной переменной используется оператор new:
new int ; // динамически выделяем целочисленную переменную и сразу же отбрасываем результат (так как нигде его не сохраняем)В примере, приведенном выше, мы запрашиваем выделение памяти для целочисленной переменной из операционной системы. Оператор new возвращает указатель, содержащий адрес выделенной памяти.
Для доступа к выделенной памяти создается указатель:
int * ptr = new int ; // динамически выделяем целочисленную переменную и присваиваем её адрес ptr, чтобы затем иметь доступ к нейЗатем мы можем разыменовать указатель для получения значения:
* ptr = 8 ; // присваиваем значение 8 только что выделенной памятиВот один из случаев, когда указатели полезны. Без указателя с адресом на только что выделенную память у нас не было бы способа получить доступ к ней.
Как работает динамическое выделение памяти?
На вашем компьютере имеется память (возможно, большая её часть), которая доступна для использования программами. При запуске программы ваша операционная система загружает эту программу в некоторую часть этой памяти. И эта память, используемая вашей программой, разделена на несколько частей, каждая из которых выполняет определенную задачу. Одна часть содержит ваш код, другая используется для выполнения обычных операций (отслеживание вызываемых функций, создание и уничтожение глобальных и локальных переменных и т.д.). Мы поговорим об этом чуть позже. Тем не менее, большая часть доступной памяти компьютера просто находится в ожидании запросов на выделение от программ.
Когда вы динамически выделяете память, то вы просите операционную систему зарезервировать часть этой памяти для использования вашей программой. Если ОС может выполнить этот запрос, то возвращается адрес этой памяти обратно в вашу программу. С этого момента и в дальнейшем ваша программа сможет использовать эту память, как только пожелает. Когда вы уже выполнили с этой памятью всё, что было необходимо, то её нужно вернуть обратно в операционную систему, для распределения между другими запросами.
В отличие от статического или автоматического выделения памяти, программа самостоятельно отвечает за запрос и обратный возврат динамически выделенной памяти.
Освобождение памяти
Когда вы динамически выделяете переменную, то вы также можете её инициализировать посредством прямой инициализации или uniform-инициализации (в С++11):
int * ptr1 = new int ( 7 ) ; // используем прямую инициализацию int * ptr2 = new int < 8 >; // используем uniform-инициализациюКогда уже всё, что требовалось, выполнено с динамически выделенной переменной — нужно явно указать для С++ освободить эту память. Для переменных это выполняется с помощью оператора delete:
// Предположим, что ptr ранее уже был выделен с помощью оператора new delete ptr ; // возвращаем память, на которую указывал ptr, обратно в операционную систему ptr = 0 ; // делаем ptr нулевым указателем (используйте nullptr вместо 0 в C++11)Оператор delete на самом деле ничего не удаляет. Он просто возвращает память, которая была выделена ранее, обратно в операционную систему. Затем операционная система может переназначить эту память другому приложению (или этому же снова).
Хотя может показаться, что мы удаляем переменную, но это не так! Переменная-указатель по-прежнему имеет ту же область видимости, что и раньше, и ей можно присвоить новое значение, как и любой другой переменной.
Обратите внимание, удаление указателя, не указывающего на динамически выделенную память, может привести к проблемам.
Висячие указатели
Язык C++ не предоставляет никаких гарантий относительно того, что произойдет с содержимым освобожденной памяти или со значением удаляемого указателя. В большинстве случаев, память, возвращаемая операционной системе, будет содержать те же значения, которые были у нее до освобождения, а указатель так и останется указывать на только что освобожденную (удаленную) память.
Указатель, указывающий на освобожденную память, называется висячим указателем. Разыменование или удаление висячего указателя приведет к неожиданным результатам. Рассмотрим следующую программу:
int * ptr = new int ; // динамически выделяем целочисленную переменную * ptr = 8 ; // помещаем значение в выделенную ячейку памяти delete ptr ; // возвращаем память обратно в операционную систему, ptr теперь является висячим указателем std :: cout << * ptr ; // разыменование висячего указателя приведет к неожиданным результатам delete ptr ; // попытка освободить память снова приведет к неожиданным результатам такжеВ программе, приведенной выше, значение 8 , которое ранее было присвоено динамической переменной, после освобождения может и далее находиться там, а может и нет. Также возможно, что освобожденная память уже могла быть выделена другому приложению (или для собственного использования операционной системы), и попытка доступа к ней приведет к тому, что операционная система автоматически прекратит выполнение вашей программы.
Процесс освобождения памяти может также привести и к созданию нескольких висячих указателей. Рассмотрим следующий пример:
int * ptr = new int ; // динамически выделяем целочисленную переменную int * otherPtr = ptr ; // otherPtr теперь указывает на ту же самую выделенную память, что и ptr delete ptr ; // возвращаем память обратно в операционную систему. ptr и otherPtr теперь висячие указатели // Однако, otherPtr по-прежнему является висячим указателем!Есть несколько рекомендаций, которые могут здесь помочь:
Во-первых, старайтесь избегать ситуаций, когда несколько указателей указывают на одну и ту же часть выделенной памяти. Если это невозможно, то выясните, какой указатель из всех «владеет» памятью (и отвечает за её удаление), а какие указатели просто получают доступ к ней.
Правило: Присваивайте удаленным указателям значение 0 (или nullptr в C++11), если они не выходят из области видимости сразу же после удаления.
Оператор new
При запросе памяти из операционной системы в редких случаях она может быть не выделена (т.е. её может и не быть в наличии).
По умолчанию, если оператор new не сработал, память не выделилась, то генерируется исключение bad_alloc . Если это исключение будет неправильно обработано (а именно так и будет, поскольку мы еще не рассматривали исключения и их обработку), то программа просто прекратит свое выполнение (произойдет сбой) с ошибкой необработанного исключения.
Во многих случаях процесс генерации исключения оператором new (как и сбой программы) нежелателен, поэтому есть альтернативная форма оператора new, которая возвращает нулевой указатель, если память не может быть выделена. Нужно просто добавить константу std::nothrow между ключевым словом new и типом данных:
int * value = new ( std :: nothrow ) int ; // указатель value станет нулевым, если динамическое выделение целочисленной переменной не выполнитсяВ примере, приведенном выше, если оператор new не возвратит указатель с динамически выделенной памятью, то возвратится нулевой указатель.
Разыменовывать его также не рекомендуется, так как это приведет к неожиданным результатам (скорее всего, к сбою в программе). Поэтому наилучшей практикой является проверка всех запросов на выделение памяти для обеспечения того, что эти запросы будут выполнены успешно и память выделится:
int * value = new ( std :: nothrow ) int ; // запрос на выделение динамической памяти для целочисленного значения if ( ! value ) // обрабатываем случай, когда new возвращает null (т.е. память не выделяется)Поскольку не выделение памяти оператором new происходит крайне редко, то обычно программисты забывают выполнять эту проверку!
Нулевые указатели и динамическое выделение памяти
Нулевые указатели (указатели со значением 0 или nullptr ) особенно полезны в процессе динамического выделения памяти. Их наличие как бы сообщаем нам: «Этому указателю не выделено никакой памяти». А это, в свою очередь, можно использовать для выполнения условного выделения памяти:
// Если для ptr до сих пор не выделено памяти, то выделяем еёУдаление нулевого указателя ни на что не влияет. Таким образом, в следующем нет необходимости:
Вместо этого вы можете просто написать:
Если ptr не является нулевым, то динамически выделенная переменная будет удалена. Если значением указателя является нуль, то ничего не произойдет.
Утечка памяти
Динамически выделенная память не имеет области видимости, т.е. она остается выделенной до тех пор, пока не будет явно освобождена или пока ваша программа не завершит свое выполнение (и операционная система очистит все буфера памяти самостоятельно). Однако указатели, используемые для хранения динамически выделенных адресов памяти, следуют правилам области видимости обычных переменных. Это несоответствие может вызвать интересное поведение, например:
Здесь мы динамически выделяем целочисленную переменную, но никогда не освобождаем память через использование оператора delete. Поскольку указатели следуют всем тем же правилам, что и обычные переменные, то, когда функция завершит свое выполнение, ptr выйдет из области видимости. Поскольку ptr — это единственная переменная, хранящая адрес динамически выделенной целочисленной переменной, то, когда ptr уничтожится, больше не останется указателей на динамически выделенную память. Это означает, что программа «потеряет» адрес динамически выделенной памяти. И в результате эту динамически выделенную целочисленную переменную нельзя будет удалить.
Это называется утечкой памяти. Утечка памяти происходит, когда ваша программа теряет адрес некоторой динамически выделенной части памяти (например, переменной или массива), прежде чем вернуть её обратно в операционную систему. Когда это происходит, то программа уже не может удалить эту динамически выделенную память, поскольку больше не знает, где выделенная память находится. Операционная система также не может использовать эту память, поскольку считается, что она по-прежнему используется вашей программой.
Хотя утечка памяти может возникнуть и из-за того, что указатель выходит из области видимости, возможны и другие способы, которые могут привести к утечкам памяти. Например, если указателю, хранящему адрес динамически выделенной памяти, присвоить другое значение:
Цель работы: 1) изучить менеджер памяти С++ 2) изучить различные способы обработки исключений; 3) получить практические навыки программирования задач с выделением памяти и обработкой исключений. Теоретические сведения
Выделение памяти
В языке программирования C++ оператор new обеспечивает выделение динамической памяти в куче. За исключением формы, называемой «размещающей формой new», new пытается выделить достаточно памяти в куче для размещения новых данных и, в случае успеха, возвращает адрес свежевыделенной памяти. Однако, если new не может выделить память в куче, то он генерирует (throw) исключение типа . Это устраняет необходимость явной проверки результата выделения.
Синтаксис new выглядит следующим образом: p_var = new typename; где p_var — ранее объявленный указатель типа typename. typename может подразумевать собой любой фундаментальный тип данных или объект, определенный пользователем (включая, enum, class и struct). Если typename — это тип класса или структуры, то он должен иметь доступный конструктор по умолчанию, который будет вызван для создания объекта. Для инициализации новой переменной, созданной при помощи new нужно использовать следующий синтаксис: p_var = new type(initializer); где initializer — первоначальное значение, присвоенное новой переменной, а если type — тип класса, то initializer — аргумент(ы) конструктора. new может также создавать массив: p_var = new type [size]; В данном случае, size указывает размерность (длину) создаваемого одномерного массива. Адрес первого элемента возвращается и помещается в p_var, поэтому p_var[n] означает значение n-го элемента (считая от нулевой позиции)
Память, выделенная при помощи new, должна быть освобождена при помощи delete, дабы избежать утечки памяти. Массивы, выделенные (созданные) при помощи new[], должны освобождаться (уничтожаться) при помощи delete[].
Инициализаторы не могут быть указаны для массивов, созданных при помощи new. Все элементы массива инициализируются при помощи конструктора по умолчанию для данного типа. Если тип не имеет конструктора по умолчанию, выделенная область памяти не будет проинициализирована.
Существует особая форма оператора new, называемая placement new. Данный оператор не выделяет память, а получает своим аргументом адрес на уже выделенную каким-либо образом память (например, на стеке или через malloc). Происходит размещение (инициализация) объекта путем вызова конструктора, и объект создается в памяти по указанному адресу. Часто такой метод применяют, когда у класса нет конструктора по умолчанию и при этом нужно создать массив объектов. Пример вызова выглядит следующим образом:
Поскольку при выделении памяти тип создаваемого объекта(ов) не был указан, компилятор не будет вызывать деструктор для каждого объекта массива, поэтому это нужно сделать вручную, перед освобождением блока памяти. Проверка выделения памяти В компиляторах, придерживающихся стандарта ISO C++, в случае если недостаточно памяти для выделения, то генерируется исключение типа std::bad_alloc. Выполнение всего последующего кода прекращается, пока ошибка не будет обработана в блоке try-catch или произойдет экстренное завершение программы. Программа не нуждается в проверке значения указателя; если не было сгенерировано исключение, то выделение прошло успешно. Реализованные операции определяются в заголовке . В большинстве реализаций C++ оператор new также может быть перегружен для определения особого поведения. Освобождение памяти В языке программирования C++ оператор delete (или delete[]) возвращает память, выделенную оператором new, обратно в кучу. Вызов delete должен происходить для каждого вызова new, чтобы избежать утечки памяти. После вызова delete объект, указывающий на этот участок памяти, становится некорректным и не должен больше использоваться. Многие программисты присваивают 0 (нуль-указатель) указателям после использования delete, чтобы минимизировать количество ошибок программирования. Однако нужно отметить, что удаление нуль-указателя фактически не имеет эффекта, так что нет необходимости проверять нуль-указатель перед вызовом delete. Фрагмент кода в качестве примера:
Установленные исключения
Синтаксически выражения throw возникает в двух формах:
Выражение throw устанавливает исключение. Выражение throw без аргумента повторно устанавливает текущее исключение. Обычно оно используется, когда для дальнейшей обработки исключения необходим второй обработчик, вызываемый из первого.
Если пользователь хочет выводить дополнительную информацию или использовать ее для принятия решения относительно действий обработчика, то допустимо формирование в виде объекта.
Теперь выражение throw может быть более информативным . throw vect_error(bounds, i, ub); . Блоки try Синтаксически блок try имеет такую форму try составной оператор список обработчиков Блок try - контекст для принятия решения о том, какие обработчики вызываются для установленного исключения.
Выражение throw соответствует аргументу catch, если он: точно соответствует. общий базовый класс порожденного типа представляет собой то, что устанавливается. объект установленного типа является типом указателя, преобразуемым в тип указателя, являющегося аргументом catch. Обработчики catch Синтаксически обработчик catch имеет следующую форму
Спецификация исключения Синтаксис Заголовок_функции throw(список типов) void foo() throw(int, over_flow); void noex(int i) throw(); Terminate() и unexpected() Обработчик terminate() вызывается, когда для обработки исключения не поставлен другой обработчик. По умолчанию вызывается функция abort(). Обработчик unexpected() вызывается, когда исключения не было в списке спецификации исключения
Работа с динамической памятью зачастую является узким местом во многих алгоритмах, если не применять специальные ухищрения.
В статье я рассмотрю парочку таких техник. Примеры в статье отличаются (например, от этого) тем, что используется перегрузка операторов new и delete и за счёт этого синтаксические конструкции будут минималистичными, а переделка программы — простой. Также описаны подводные камни, найденные в процессе (конечно, гуру, читавшие стандарт от корки до корки, не удивятся).
0. А нужна ли нам ручная работа с памятью?
В первую очередь проверим, насколько умный аллокатор может ускорить работу с памятью.
1. Пул объектов
Очевидное решение — забрать у ОС большой блок памяти и разбить его на равные блоки размера sizeof(Node), при выделении памяти брать блок из пула, при освобождении — возвращать в пул. Пул проще всего организовать с помощью односвязного списка (стека).
Поскольку стоит задача минимального вмешательства в программу, всё что можно будет сделать, это добавить примесь BlockAlloc к классу Node:
Прежде всего нам понадобится пул больших блоков (страниц), которые забираем у ОС или C-runtime. Его можно организовать поверх функций malloc и free, но для большей эффективности (чтобы пропустить лишний уровень абстракции), используем VirtualAlloc/VirtualFree. Эти функции выделяют память блоками, кратными 4K, а также резервируют адресное пространство процесса блоками, кратными 64K. Одновременно указывая опции commit и reserve, мы перескакиваем ещё один уровень абстракции, резервируя адресное пространство и выделяя страницы памяти одним вызовом.
Затем организуем пул блоков заданного размера
Комментарием // todo: lock(this) помечены места, которые требуют межпоточной синхронизации (например, используйте EnterCriticalSection или boost::mutex).
Объясню, почему при «форматировании» страницы не ипользуется абстракция FreeBlock для добавления блока в пул. Если бы было написано что-то вроде
То страница по принципу FIFO оказалась бы размеченной «наоборот»:
Несколько блоков, затребованных из пула подряд, имели бы убывающие адреса. А процессор не любит ходить назад, от этого у него ломается Prefetch (UPD: Не актуально для современных процессоров). Если же делать разметку в цикле
то цикл разметки ходил бы по адресам назад.
Теперь, когда приготовления сделаны, можно описать класс-примесь.
Объясню, зачем нужны проверки if (s != sizeof(T))
Когда они срабатывают? Тогда, когда создаётся/удаляется класс, отнаследованный от базового T.
Наследники будут пользоваться обычными new/delete, но к ним также можно примешать BlockAlloc. Таким образом, мы легко и безопасно определяем, какие классы должны пользоваться пулами, не боясь сломать что-то в программе. Множественное наследование также прекрасно работает с этой примесью.
2. Контейнер и его пёстрое содержимое
Часто ли попадаются классы, которые хранят в себе массу различных дочерних объектов, таких, что время жизни последних не дольше времени жизни родителя?
Например, это может быть класс XmlDocument, наполненный классами Node и Attribute, а также c-строками (char*), взятыми из текста внутри нод. Или список файлов и каталогов в файловом менеджере, загружаемых один раз при перечитывании каталога и больше не меняющихся.
Как было показано во введении, delete обходится дороже, чем new. Идея второй части статьи в том, чтобы память под дочерние объекты выделять в большом блоке, связанном с Parent-объектом. При удалении parent-объекта у дочерних будут, как обычно, вызваны деструкторы, но память возвращать не потребуется — она освободиться одним большим блоком.
Создадим класс PointerBumpAllocator, который умеет откусывать от большого блока куски разных размеров и выделять новый большой блок, когда старый будет исчерпан.
Наконец, опишем примесь ChildObject с перегруженными new и delete, обращающимися к заданному аллокатору:
new (… параметры для оператора… ) ChildObject (… параметры конструктора… )
Для удобства я задал два оператора new, принимающих A& или A*.
Если аллокатор добавлен в parent-класс как член, удобнее первый вариант:
Если аллокатор добавлен как предок (примесь), удобнее второй:
Понятно, что указатель и ссылка взаимно конвертируются, разделение этих случаев — избавления от лишних значков.
Для вызова delete не предусмотрен специальный синтаксис, компилятор вызовет стандартный delete (отмеченный *1), независимо от того, какой из операторов new был использован для создания объекта. То есть, синтаксис delete обычный:
Если же в конструкторе ChildObject (или его наследника) происходит исключение, вызывается delete с сигнатурой, соответствующей сигнатуре оператора new, использованном при создании этого объекта (первый параметр size_t будет заменён на void*).
Размешение оператора new в секции private защищает от вызова new без указания аллокатора.
Приведу законченный пример использования пары Allocator-ChildObject:
Заключение. Статья была написана 1.5 года назад для песочницы, но увы, не понравилась модератору.
Читайте также: